diff --git a/.chezmoi.toml.tmpl b/.chezmoi.toml.tmpl index fd8bfe1..e90cfd2 100644 --- a/.chezmoi.toml.tmpl +++ b/.chezmoi.toml.tmpl @@ -8,7 +8,7 @@ {{- $interactive := stdinIsATTY -}} {{- $name := promptStringOnce . "name" "Name" -}} {{- $email := promptStringOnce . "email" "Email" -}} -{{- $signingKeyOpReference := promptStringOnce . "signingKeyOpReference" "public key op:// secret reference" | trimAll "\"" -}} +{{- $is_work := promptBoolOnce . "is_work" "Is this a work machine" -}} {{- if $interactive -}} {{- writeToStdout "💡 Tip: you can re-enter your name and email with `chezmoi init --data=false`.\n" -}} {{- end -}} @@ -16,15 +16,20 @@ {{- if eq .chezmoi.os "darwin" -}} {{- $computerName = output "scutil" "--get" "ComputerName" | trim -}} {{- end -}} -{{- $signingKey := onepasswordRead $signingKeyOpReference -}} sourceDir = "{{ .chezmoi.sourceDir -}}" +encryption = "age" + +[age] + identity = {{ joinPath .chezmoi.homeDir ".config/chezmoi/key.txt" | quote }} + recipient = "age17ngtqly39w3rgxne0f06f84ss8rq9qpk697vye9qqcywmutu9a2qum55v0" + [data] name = {{ $name | quote}} email = {{ $email | quote }} is_wsl = {{ $is_wsl }} computer_name = {{ $computerName | quote }} os = {{ .chezmoi.os | quote }} - signingkey = {{ $signingKey | quote }} + is_work = {{ $is_work }} [git] autoCommit = true diff --git a/.chezmoidata.toml b/.chezmoidata.toml new file mode 100644 index 0000000..e7fd55f --- /dev/null +++ b/.chezmoidata.toml @@ -0,0 +1,6 @@ +work_email = "jack.platten@agilebits.com" + +[keys] + personal = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHGminsJAXUJkc7TH7qHU6RNdZuMcWIwdx+zZCDpDiUG" + work = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPdfrqOCDx1cjTn3ZFITw82y2ujZMFFaS9qowOeQEzpd" + emu = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEi0B051rYqFXUEhqbH9PfLEnB5yY5m0GqcmpWhfW5wm" diff --git a/.chezmoiignore b/.chezmoiignore index 39cb021..69df27e 100644 --- a/.chezmoiignore +++ b/.chezmoiignore @@ -1,4 +1,15 @@ .github/ install.sh LICENSE -README.md \ No newline at end of file +README.md +{{ if .is_work }} +.config/halloy +{{ else }} +.config/git/github-work.conf +.config/git/github-emu.conf +.ssh/id_ed25519-emu.pub +.ssh/config.d +.config/git/work.conf +.config/jj/conf.d +.config/fish/conf.d/work.fish +{{ end }} diff --git a/private_dot_config/git/config.tmpl b/private_dot_config/git/config.tmpl index 53abab1..5526f30 100644 --- a/private_dot_config/git/config.tmpl +++ b/private_dot_config/git/config.tmpl @@ -6,9 +6,14 @@ defaultBranch = main [user] - email = "{{ .email }}" name = "{{ .name }}" - signingkey = "{{ .signingkey }}" +{{- if .is_work }} + email = "{{ .work_email }}" + signingkey = "key::{{ .keys.emu }}" +{{- else }} + email = "{{ .email }}" + signingkey = "key::{{ .keys.personal }}" +{{- end }} [alias] # one-line log @@ -87,9 +92,13 @@ path = "{{ $github_file }}" [includeIf "hasconfig:remote.*.url:git@github.com:*/**"] path = "{{ $github_file }}" -[includeIf "hasconfig:remote.*.url:ssh://git@github.com:*/**"] +[includeIf "hasconfig:remote.*.url:ssh://git@github.com/**"] path = "{{ $github_file }}" -[includeIf "hasconfig:remote.*.url:ssh://git@gitlab.com:*/**"] +{{- if .is_work }} +[include] + path = "{{ joinPath $dir "work.conf" }}" +{{- end }} +[includeIf "hasconfig:remote.*.url:ssh://git@gitlab.com/**"] path = "{{ $gitlab_file }}" [includeIf "hasconfig:remote.*.url:https://gitlab.com/**"] path = "{{ $gitlab_file }}" diff --git a/private_dot_config/git/encrypted_work.conf.age b/private_dot_config/git/encrypted_work.conf.age new file mode 100644 index 0000000..b8b4a71 --- /dev/null +++ b/private_dot_config/git/encrypted_work.conf.age @@ -0,0 +1,30 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTVEtvOW5FZWxqQkhNK3R0 +Wkg1RXFWMDZjZENVSVBhLzUrWHMzRklWeGxBCkh5T0VxTjNOQnFBL0R6ZXozU1lQ +MjdxWXA3cC9Za0p3VzlNdmJFQWdhbTAKLS0tIFdidUhjdFVUWkZSM05OamgrcG9T +WWVQTG0vQ01sbmJmeG9DZ0tUeXBvZFEK1ZBFJaktZJ/TB+xJJIWoiQtD1CbOYklV +isE2MaepOWVY1jbrny0hLxJUKFxuF/9DXS1OqJAb6vFB7lxb4OLcHqqbG+pBT+f4 +MUtgXPd91qmold6bydL/9ZH8KdGZ7WVMgVEwNkQ/M6QK3K3h2wlvaCQsje5fl14q +p+UR+M6CA24i3IR/lxAhhRQWGMNSW0g644RjU07dIJCIAaJbwZrxm2hdNBxJ9HDJ +0InAAgXyzgt6nzH7TTeCeLenNRk4zF7WKPZPvkWApgchG8ESn7gH1GdxZm/z6gR6 +AZspo/1e8Mlwa/jgRpb+04tWqBo0fGjYBISjgjZXbKBUrVZ7uuzK1vvqOJz+CMQc +15TOkFZ4tsS494uapMBn42M+Bp+JKr8ioVEZWEbAV8nKSe0OYQa2RQqB9altjKxF +K/6NAalpxuxDoqu9b58tb3/+5RId11vg2V+bSs7KXKp0QFCdBuPlNGZwqIrlDBQi +Xov1R91yFSn8Q4gEPyGo/Lpn0i6XrVA2cFLDOKzBkMSPKOhkzXppTrlwF02WZo95 +XyxGHFWzxebI++CTW48Qymlte1upnq4z1b6y2NKCNl4n4tSEIh8r6dkL8Qga27NT +PZnjx0SQc/C7fqmSfW38K5v7aTH1xTwoyTpWtG33eK0uI22a8u2Smg9I7ZOPEBWU +ZLktaOrpyxXbMIxT/KoV4Rn1YeW/W9yyrMvY3uAVavfbKvCvMRI+NA9xFIbcbl4y +HwEw6h9GyAgnnyhKoSG3bNtgEO2/VoAD7hySDDz6RMbxiXE8aUjypCmEWnuf0IaQ +0L6Fg5/F88LocLkYGhm1Kkf+QIRyma4CkNzlqdIgY26R4ppY37HE/ICaBHG7hX3e +pEN254btBRCWvqxnVzh2EFvJ7EuKoF3dPEeBGe7YQyV9R+KzwM199lC/aIwa3BGP +X11e6PjUWcxrwpuWO5ij6/TGuiXVPIPCPfrNq71u2AXc8APc3ry/s4M/CiyrC+Fc +tLanMglCjBg+jXhmQjRBpFt+ZxzyJr3P9jeU8PF2Xaujn+q/WO1zm7QYJ2wCuPot +rpWdpgDmt7S5a7Nk/82bLQquUiEuFlrnld+kqKWwR276arvhGZCLjYfFJuur3VCA +5tC/oj60w9hXs9E1i9/fFAvKRWDVvfFXvDxj/mf0vvqE7xN0JA4XLA9Hb9IC/UUz +PwDmQJ87UEmrCt5qg8kWjJz40dQH0WfS8t0eLuOCAY6vwR/AGh1sme4rTTkfLGp0 +Nkc4Q2584HmJVXFxa+3g+yn+NlsXziXsmjznq1Hkbd72UcZgRM+AxaQy3mUGCMB6 +c+6FEG2hyW9lueZpBSZcxlM5jByfm/AOhidYQWNE3e6gue0Vfya5jEoDbmbUU3mh +AxE9saTmdbipmUJbwzMI9ko5Jb4Y2AqRMHCmvnN+Z9quxOZMf6t6KuTFbZuICNV/ +CbYRLZdGInADGdUk8cEYvWyvp0Km0FR0ibWC9Yprd+fC3/YcumhnnCdQOIIGWf5r +6sub7uD7pmyepW5T1OV6 +-----END AGE ENCRYPTED FILE----- diff --git a/private_dot_config/git/github-emu.conf.tmpl b/private_dot_config/git/github-emu.conf.tmpl new file mode 100644 index 0000000..aec205e --- /dev/null +++ b/private_dot_config/git/github-emu.conf.tmpl @@ -0,0 +1,4 @@ +[user] + email = "{{ .work_email }}" + name = "{{ .name }}" + signingkey = "key::{{ .keys.emu }}" diff --git a/private_dot_config/git/github-work.conf.tmpl b/private_dot_config/git/github-work.conf.tmpl new file mode 100644 index 0000000..43aacc1 --- /dev/null +++ b/private_dot_config/git/github-work.conf.tmpl @@ -0,0 +1,4 @@ +[user] + email = "{{ .work_email }}" + name = "{{ .name }}" + signingkey = "key::{{ .keys.work }}" diff --git a/private_dot_config/git/github.conf.tmpl b/private_dot_config/git/github.conf.tmpl index f74427d..eaf513c 100644 --- a/private_dot_config/git/github.conf.tmpl +++ b/private_dot_config/git/github.conf.tmpl @@ -1,4 +1,4 @@ [user] email = "{{ .email }}" name = "{{ .name }}" - signingkey = "{{ .signingkey }}" + signingkey = "key::{{ .keys.personal }}" diff --git a/private_dot_config/git/gitlab.conf.tmpl b/private_dot_config/git/gitlab.conf.tmpl index f74427d..eaf513c 100644 --- a/private_dot_config/git/gitlab.conf.tmpl +++ b/private_dot_config/git/gitlab.conf.tmpl @@ -1,4 +1,4 @@ [user] email = "{{ .email }}" name = "{{ .name }}" - signingkey = "{{ .signingkey }}" + signingkey = "key::{{ .keys.personal }}" diff --git a/private_dot_config/jj/conf.d/encrypted_work.toml.age b/private_dot_config/jj/conf.d/encrypted_work.toml.age new file mode 100644 index 0000000..59bb4ca --- /dev/null +++ b/private_dot_config/jj/conf.d/encrypted_work.toml.age @@ -0,0 +1,23 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5Mi9SVFhyTGVUbVd6a2NC +dnRnOTNRU2Y4ZzFRR0ZNQU5mSGxuZXhvNmhzCkFFQVBwRVpzTlpNUDM2WlMxTThD +YjRKc3F4Q3cybUt3M0EvZ3IwMjJ2RU0KLS0tIHRFaWZhLzB6aVc1ZmxhbG9rOVZ0 +SGtmZUF2QkVsQ3pKVDJaZ1lrYi9qMEkKM8IQVE6xAecydnudaTBse0OaLvwbElsZ +LCd9Csd6Y+GOyA7XJNZAPhuhig3DTF/2OoNraC5NdEVxlDDGYAaUWx1lMG4Y6OUm +qVmWTIqoHIthF+TGKK4O2GwGSM4OFahTFZ/pH1utEcfT0wLHqB47ykJQeUqSgTZb +9iV6z4SVg69uo06cCeKkN5UtkzbjmRxnFrKgdeioWYkWpcGNE4dvZMVp5rIfzhMY +ct+buoUrqgNE9Vv2nb6Aed2Eg/DUZALMfwTdHqNDnjBd9e8XKM0AOwHBRWvXjkkc +GRayqcLO6tor9W7IJtOYbRiZ+Eb0qsHa344c6gg8DzTQdCbURepEH6GobA57ql2+ +WcXhJ6Q3Hx7YEZP9rK66hYuCmc/eTRYXxg15XICn0vDPNNizYL7qwFqYjsIjL7Xy +nn8iZFnziMbwkd88wMOXYDX5qK30uUqDWCKf5jblXy7fHDgzmO2vaOaqDXkzzyRY +ifDv4FahSVlT/2qASNPo41ieb1mryow8ohYv9smLfzNJKPkSQheDawo+BerqPkUp +Q5rUJvYj4HIp1PD5yWFjbWdmR5uFqsld6q0lHAH9+uFahcTUEsw0RmO4IMAbp90C +1c6VQKM62J41ujgOuySrdGA44SikaM3gE2fVQ1tW3JWGQCA4c2b6i4dPZPUe1eh0 +X/K//SxQLEAwOdOncgvN5CtgAMBx4ar+ZlBGxyompVDEpK6dGo5FFa0bp64tSy6t +r1EusMj7/mmgdIEPY7BIYkGZIrh92Hx8Cnz/OIXCB44dpLy74eDiG9oPahJY11GB +sZA44bkOLhjWSTomHe8vwISkQ3CGQ4JOc2KwDpeIQY4dqo1nPxvrtjAxb8He3OjA +q4gSG6icjDVzq7yqZqzydju7kJONJHKwhq1MblTGHoklpKFzWf3OuZ9bFauLqVBD +x77kMa5BS9FcWmPijRaihR+OITU6QhObh1THUNyoTBwhux9IOaeGJCDJZYXeUcAR +y93K95UBUcnY+qNg12iQn1Z547sXXBLrpINzvhPuTrlRfMJ50ogy4F+RARhe/Wxu +lZoN/ynxTeVbYuTeEcZFrEhtm+dCQ6yQtZ2RAvmXwhV77vYCpF4GrMvu602SOQ== +-----END AGE ENCRYPTED FILE----- diff --git a/private_dot_config/jj/config.toml.tmpl b/private_dot_config/jj/config.toml.tmpl index 22012f4..4ead970 100644 --- a/private_dot_config/jj/config.toml.tmpl +++ b/private_dot_config/jj/config.toml.tmpl @@ -2,7 +2,7 @@ [user] name = "{{ .name }}" -email = "{{ .email }}" +email = "{{ if .is_work }}{{ .work_email }}{{ else }}{{ .email }}{{ end }}" [ui] default-command = "log" @@ -23,7 +23,7 @@ merge-args = ["--merge", "--output", "$output", "--base", "$base", "$left", "$ri [signing] behavior = "drop" backend = "ssh" -key = "{{ .signingkey }}" +key = "{{ if .is_work }}{{ .keys.emu }}{{ else }}{{ .keys.personal }}{{ end }}" backends.ssh.allowed-signers = {{joinPath .chezmoi.homeDir ".ssh" "allowed_signers" | quote}} diff --git a/private_dot_config/private_fish/conf.d/encrypted_work.fish.age b/private_dot_config/private_fish/conf.d/encrypted_work.fish.age new file mode 100644 index 0000000..0bca6bf --- /dev/null +++ b/private_dot_config/private_fish/conf.d/encrypted_work.fish.age @@ -0,0 +1,15 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLZHd5dEwrcGF2cGI4QlRi +Q213QmhYODdRQ3BYK0E2UjdkTDM3aWs2Y0JVCko2ZkFIYlNsRlB1RitnbnhpSGk4 +L2E1TVpJRjFiV05WMlRDT0ZTdGdjZk0KLS0tIFpYdVVuZmZxQzNaRnAyTlBIK0pn +eDdkeDFSNm16ZHpzM3BrVUo0cHlGcHMK8HF8dI/DyGw02NapoPXOvvaM5o/3qwiM +U/XCvNxopFXPRZ1OIDWAgPBUMMKvjaoz10NogOBDUQzVqNT7ey9H18hCSO1SErs4 +F94HAbkC8ZjjBHuo7Kn8vc2fDN1M6q1S3ScI1k8KJxShX8XE3peL8D5/aGjMfLvM +pWoS1STh/CXT11i8+X2JNlBCNJ0e6Fprp421rnWRR3nEWAk4HVur3BvyLNwOxJyu +BgXTNp3jVGuP+aDiCQenLfGqhT5aqouGBBLFJzl/CarZl2hCCzt9n2Op/FO8l2w1 +OkfmN4V/bsAulHjeeH/dFCG6CaSUHbz5QCjxy9V6Kn58pM/bFDFV1ucgtaMrHN0o +KZclFYLSYdTgf4gotNadnDVvpk5xnvvIqiXvjiUnVu21UXibjsQzRO2nyEXBuYnO +4k1n2Fvq/qkAujEZdhTNWtIDT3wVXq9NYt6YAp83/7H2fPlHXeg0skUEzXeJNa4F +CZb3z2xJljfVTo4XPvm4Nr/emHqc+smvnUvbWg1l7GLeO7og/Qrt3Lrs/Quwdple +Gmo= +-----END AGE ENCRYPTED FILE----- diff --git a/private_dot_ssh/config.d/encrypted_private_work.age b/private_dot_ssh/config.d/encrypted_private_work.age new file mode 100644 index 0000000..178e703 --- /dev/null +++ b/private_dot_ssh/config.d/encrypted_private_work.age @@ -0,0 +1,17 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxd1hiMHZ3cS9ESFVqSkpi +ODYvZUU4T2pjemV1endLbUxNMnBKWDR0eWo4ClRyY3FBL2ZIb3hBc0phUFlEZ016 +T29GUjQycHlvSHROOTVtR0VVVlFITjAKLS0tIEpmOWk2NGlZNEVlZjZhd2RzUUZz +cU5ITSs5K2pNaUkwMlB4VE1jMGcvT1EKqiEwfHaQ+QZed65ipuE/DLquBA50C2dr +cgQMoKi/Gujhgi3nPlE4SSG0xDhWvdEFnOWle3bzSaJMw8iYiUbPZPSxWpiwXcMY +2hJX0w0IrCPImyJZPDZwD5ykayhypQA4meP0TasMhrc1ehK0/jiQGIPJCORTcwjo +ZSgHkHHysOS9QcHU9Zdy4tX2PxPhTcmeOEJ5cMjH6qIK/IeeyE00m/iBD0IOXAXJ +wY7/q83tlu1ZrKrX+ofe1xvqTPKuDQZbpephF71SmUOb6TKObYdin5SLBWGoMpED +F6T8cN8rFUpX9BkYHST29XANe7fBp18b+D6UPzUDIiQjxKu5j78E+SnaHpod5SnO +m3yokkgwxU4tMlnVuJY0EMwd8BJgkVPOPBI0YFSi4yX3oPE+i0bQLmAjrdA19a6R +JkUfvN/G+Knq/hZi/saY679igvO5IZe725mm4y8aBJ1GPvSdTrEmFGvJKxbXdXR6 +EAWAcOf0U3CUq9YCGJIzAM/g0fcMSbGMaeEEx5AVtSgfcYJ/6RzYM8spwEXzKW+F +xUPBF9P4dzEIwacUsqQ8m+b+Eg20+DvLQE/A7LdLkk8Vj9j7vNt1JMp4Xvai1/fG +I9pL5QwOdq3qnFk8NOpuBJTKCWVxcRDNi2+lc3Atvu9/tQfYWtHoG9xppz/ZiWF3 +wkfckr+Ww6PZCP8uFykhbuleRLTM3UxpQpo= +-----END AGE ENCRYPTED FILE----- diff --git a/private_dot_ssh/private_allowed_signers.tmpl b/private_dot_ssh/private_allowed_signers.tmpl index b6d1a0d..35bc371 100644 --- a/private_dot_ssh/private_allowed_signers.tmpl +++ b/private_dot_ssh/private_allowed_signers.tmpl @@ -1 +1,5 @@ -{{- .email }} namespaces="git" {{ .signingkey }} \ No newline at end of file +{{ .email }} namespaces="git" {{ .keys.personal }} +{{- if .is_work }} +{{ .work_email }} namespaces="git" {{ .keys.work }} +{{ .work_email }} namespaces="git" {{ .keys.emu }} +{{- end }} diff --git a/private_dot_ssh/private_config b/private_dot_ssh/private_config.tmpl similarity index 90% rename from private_dot_ssh/private_config rename to private_dot_ssh/private_config.tmpl index 6634d62..785c775 100644 --- a/private_dot_ssh/private_config +++ b/private_dot_ssh/private_config.tmpl @@ -2,6 +2,7 @@ # This only works if it's at the top of ssh_config (before any Host blocks). # This won't be added again if you remove it. Include ~/.orbstack/ssh/config +Include ~/.ssh/config.d/* Host unifi SetEnv TERM=xterm-256color diff --git a/private_dot_ssh/private_id_ed25519-emu.pub b/private_dot_ssh/private_id_ed25519-emu.pub new file mode 100644 index 0000000..707792a --- /dev/null +++ b/private_dot_ssh/private_id_ed25519-emu.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEi0B051rYqFXUEhqbH9PfLEnB5yY5m0GqcmpWhfW5wm \ No newline at end of file diff --git a/run_onchange_before_fetch-age-key.sh.tmpl b/run_onchange_before_fetch-age-key.sh.tmpl new file mode 100644 index 0000000..034a344 --- /dev/null +++ b/run_onchange_before_fetch-age-key.sh.tmpl @@ -0,0 +1,11 @@ +#!/bin/sh +# Fetch the age key from 1Password once, so chezmoi can decrypt encrypted files. +set -eu +key={{ joinPath .chezmoi.homeDir ".config/chezmoi/key.txt" | quote }} +[ -s "$key" ] && exit 0 +mkdir -p "$(dirname "$key")" +umask 077 +tmp="$key.tmp" +op read --account JF3PQH3C6FAA3D3KNN6BYSN3XU "op://osd4kgoldxrcodghtgcnptqsvy/slin24iodrktqhvdwo5dy3qzyy/notesPlain" > "$tmp" +grep -q '^AGE-SECRET-KEY-' "$tmp" || { rm -f "$tmp"; echo "1Password item has no age secret key" >&2; exit 1; } +mv "$tmp" "$key" -- 2.51.2 From 9f66d22473bd023d24cfd00c69d1f6b002f77254 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Wed, 7 Oct 2026 08:44:08 -0700 Subject: [PATCH 02/16] Merge this machine's config and split work/personal tools - Merge config.fish, git, jj, and ssh config with the work laptop's versions; use $HOME instead of hardcoded home paths - Prompt for the work email on work machines instead of storing it - Split mise tools: zig, rendercv, and vit are personal-only - Drop lx, httpie, and the http/https completions - Drop forgit and copilot.fish from fish plugins - Ghostty: disable contextual alternates - Fix the Icon[\r] pattern in the global gitignore Co-Authored-By: Claude Opus 5.5 --- .chezmoi.toml.tmpl | 5 + .chezmoidata.toml | 2 - private_dot_config/ghostty/config.ghostty | 2 +- private_dot_config/git/config.tmpl | 31 +- private_dot_config/git/ignore | 3 +- private_dot_config/httpie/config.json | 5 - private_dot_config/jj/config.toml.tmpl | 11 + private_dot_config/lx/config.toml | 291 ------------------ .../mise/{config.toml => config.toml.tmpl} | 5 +- .../private_fish/completions/http.fish | 107 ------- .../private_fish/completions/https.fish | 2 - .../private_fish/completions/la.fish | 1 - .../private_fish/completions/ll.fish | 1 - .../private_fish/completions/lll.fish | 1 - .../private_fish/completions/ls.fish | 1 - .../private_fish/completions/lx.fish | 120 -------- .../private_fish/completions/tree.fish | 1 - private_dot_config/private_fish/config.fish | 26 +- private_dot_config/private_fish/fish_plugins | 2 - private_dot_ssh/private_config.tmpl | 6 + 20 files changed, 77 insertions(+), 546 deletions(-) delete mode 100644 private_dot_config/httpie/config.json delete mode 100644 private_dot_config/lx/config.toml rename private_dot_config/mise/{config.toml => config.toml.tmpl} (79%) delete mode 100644 private_dot_config/private_fish/completions/http.fish delete mode 100644 private_dot_config/private_fish/completions/https.fish delete mode 100644 private_dot_config/private_fish/completions/la.fish delete mode 100644 private_dot_config/private_fish/completions/ll.fish delete mode 100644 private_dot_config/private_fish/completions/lll.fish delete mode 100644 private_dot_config/private_fish/completions/ls.fish delete mode 100644 private_dot_config/private_fish/completions/lx.fish delete mode 100644 private_dot_config/private_fish/completions/tree.fish diff --git a/.chezmoi.toml.tmpl b/.chezmoi.toml.tmpl index e90cfd2..cf15ce9 100644 --- a/.chezmoi.toml.tmpl +++ b/.chezmoi.toml.tmpl @@ -9,6 +9,10 @@ {{- $name := promptStringOnce . "name" "Name" -}} {{- $email := promptStringOnce . "email" "Email" -}} {{- $is_work := promptBoolOnce . "is_work" "Is this a work machine" -}} +{{- $work_email := "" -}} +{{- if $is_work -}} +{{- $work_email = promptStringOnce . "work_email" "Work email" -}} +{{- end -}} {{- if $interactive -}} {{- writeToStdout "💡 Tip: you can re-enter your name and email with `chezmoi init --data=false`.\n" -}} {{- end -}} @@ -30,6 +34,7 @@ encryption = "age" computer_name = {{ $computerName | quote }} os = {{ .chezmoi.os | quote }} is_work = {{ $is_work }} + work_email = {{ $work_email | quote }} [git] autoCommit = true diff --git a/.chezmoidata.toml b/.chezmoidata.toml index e7fd55f..2aa9980 100644 --- a/.chezmoidata.toml +++ b/.chezmoidata.toml @@ -1,5 +1,3 @@ -work_email = "jack.platten@agilebits.com" - [keys] personal = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHGminsJAXUJkc7TH7qHU6RNdZuMcWIwdx+zZCDpDiUG" work = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPdfrqOCDx1cjTn3ZFITw82y2ujZMFFaS9qowOeQEzpd" diff --git a/private_dot_config/ghostty/config.ghostty b/private_dot_config/ghostty/config.ghostty index 3ebeb4e..0a67455 100644 --- a/private_dot_config/ghostty/config.ghostty +++ b/private_dot_config/ghostty/config.ghostty @@ -4,7 +4,7 @@ font-size = 14 font-family = Berkeley Mono # Disable ligatures -# font-feature = -calt +font-feature = -calt # font-feature = -liga # font-feature = -dlig # Config generated by Ghostty Config diff --git a/private_dot_config/git/config.tmpl b/private_dot_config/git/config.tmpl index 5526f30..4d9f321 100644 --- a/private_dot_config/git/config.tmpl +++ b/private_dot_config/git/config.tmpl @@ -5,6 +5,9 @@ [init] defaultBranch = main +[include] + path = {{ joinPath .chezmoi.homeDir ".local/share/delta/themes.gitconfig" | quote }} + [user] name = "{{ .name }}" {{- if .is_work }} @@ -42,7 +45,6 @@ # `git log` with patches shown with difftastic. dl = -c diff.external=difft log -p --ext-diff # Show the most recent commit with difftastic. - ds = -c diff.external=difft show --ext-diff # `git diff` with difftastic. dft = -c diff.external=difft diff @@ -51,6 +53,27 @@ klog = "!f() { GIT_EXTERNAL_DIFF=ksdiff git log -p --ext-diff; }; f"; dffk = "difftool -t kale"; + # staging / committing + a = add + ap = add -p + c = commit --verbose + ca = commit -a --verbose + cm = commit -m + cam = commit -a -m + m = commit --amend --verbose + + # diff / status + d = diff + ds = diff --stat + dc = diff --cached + s = status -s + + # branches + co = checkout + cob = checkout -b + + last = log -1 HEAD --stat + # list aliases la = "!git config -l | grep alias | cut -c 7-" @@ -66,11 +89,16 @@ diffFilter = delta --color-only [delta] + features = woolly-mammoth navigate = true light = false [merge] conflictstyle = diff3 + tool = kale + +[mergetool] + prompt = false [diff] external = difft @@ -121,6 +149,7 @@ [difftool] prompt = false + trustExitCode = true [pager] # Use a pager if the difftool output is larger than one screenful, diff --git a/private_dot_config/git/ignore b/private_dot_config/git/ignore index 4766ebd..059bfe9 100644 --- a/private_dot_config/git/ignore +++ b/private_dot_config/git/ignore @@ -3,8 +3,7 @@ __MACOSX/ .AppleDouble .LSOverride -Icon[ -] +Icon[ ] # Thumbnails ._* diff --git a/private_dot_config/httpie/config.json b/private_dot_config/httpie/config.json deleted file mode 100644 index ce08b98..0000000 --- a/private_dot_config/httpie/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "default_options": [ - "--style=pie-dark" - ] -} diff --git a/private_dot_config/jj/config.toml.tmpl b/private_dot_config/jj/config.toml.tmpl index 4ead970..f3f21f7 100644 --- a/private_dot_config/jj/config.toml.tmpl +++ b/private_dot_config/jj/config.toml.tmpl @@ -9,6 +9,7 @@ default-command = "log" diff-formatter = ["difft", "--color=always", "--width=$width", "$left", "$right"] show-cryptographic-signatures = true diff-editor = ":builtin" +editor = "nano" # fixes nerd icons in less pager pager = { command = ["less", "-FRX"], env = { LESSCHARSET = "utf-8", LESSUTFCHARDEF = "E000-F8FF:p,F0000-FFFFD:p,100000-10FFFD:p" } } {{ if eq .chezmoi.os "darwin" -}} @@ -20,6 +21,16 @@ merge-args = ["--merge", "--output", "$output", "--base", "$base", "$left", "$ri {{- end }} +[merge-tools.jayjay] +program = "jayjay" +diff-args = ["tool", "diff", "$left", "$right"] +edit-args = ["tool", "edit", "$left", "$right"] +merge-args = ["tool", "merge", "$left", "$base", "$right", "$output", "$path", "$marker_length"] +merge-tool-edits-conflict-markers = true + +[templates] +draft_commit_description = "builtin_draft_commit_description_with_diff" + [signing] behavior = "drop" backend = "ssh" diff --git a/private_dot_config/lx/config.toml b/private_dot_config/lx/config.toml deleted file mode 100644 index f9af6f3..0000000 --- a/private_dot_config/lx/config.toml +++ /dev/null @@ -1,291 +0,0 @@ -## lx configuration file -## Generated by: lx --init-config -## Documentation: https://github.com/wjv/lx -version = "0.6" - - -## ── Formats ─────────────────────────────────────────────────────── -## -## A format defines a column layout. These are the compiled-in -## defaults; edit to customise. -## -## -l uses format "long" (tier 1: basic long listing) -## -ll uses format "long2" (tier 2: adds group and VCS) -## -lll uses format "long3" (tier 3: all timestamps and metadata) - -[format] -long = ["perms", "size", "user", "modified"] -long2 = ["perms", "size", "user", "group", "modified", "vcs"] -long3 = [ - "perms", - "links", - "size", - "blocks", - "user", - "group", - "modified", - "changed", - "created", - "accessed", - "vcs", -] - -## Custom formats — define your own named column layouts: - -# minimal = ["perms", "size", "modified"] -# hpc = "perms,size,user,group,modified,vcs" - - -## ── Personalities ───────────────────────────────────────────────── -## -## A personality bundles format, columns, and settings under a name. -## Invoke with --personality=NAME, -p NAME, or via argv[0] symlink. -## -## Every CLI flag has a corresponding config key: -## --header → header = true -## --sort=age → sort = "age" -## --time-style=iso → time-style = "iso" -## -## Personalities inherit from each other with `inherits = "NAME"`. -## The child's format/columns override the parent; settings merge -## with the child's values winning. -## -## default ──┬──→ lx ──┬──→ ll ──→ la -## │ └──→ lll -## └──→ tree -## -## ls (standalone — no `inherits`) - -## Shared base: settings that all personalities inherit. -[personality.default] -color = "auto" # always / auto / never -gradient = "all" # none / 16 / 256 -time-style = "default" # default / iso / long-iso / full-iso -group-dirs = "none" # first / last / none -icons = "never" # always / auto / never -classify = "auto" # always / auto / never -# theme = "lx-24bit" # named theme (see below) -#smooth = true -[[personality.default.when]] -env.TERM = "*-256color" # glob match — any 256-colour terminfo -theme = "lx-256" - -[[personality.default.when]] -env.COLORTERM = ["truecolor", "24bit"] # array — any element matches -theme = "lx-24bit" - -## On macOS, listxattr is disproportionately expensive (it dominates -## tree traversal time on APFS). Disable the `@` indicator probe by -## default; users who want the indicator can override per-personality -## with `xattr-indicator = true`, or pass `-l@` per invocation. - -[[personality.default.when]] -platform = "macos" -xattr-indicator = false - - -## The "lx" personality applies when the binary is invoked as "lx". -[personality.lx] -inherits = "default" - -[personality.ll] -inherits = "lx" -format = "long2" -group-dirs = "first" - -[personality.lll] -inherits = "lx" -format = "long3" -group-dirs = "first" -header = true -time-style = "long-iso" - -[personality.la] -inherits = "ll" -all = true - -[personality.tree] -inherits = "default" -format = "long2" -group-dirs = "first" -level = 2 -tree = true - -[personality.ls] -grid = true -across = false -## (no inherits — standalone, mimics plain ls) - -## Custom personalities — define your own and symlink lx to the name -## for argv[0] dispatch. - -[personality.lt] -inherits = "ll" -group-dirs = "none" -sort = "age" - -# [personality.recent] -# format = "long" -# sort = "modified" -# reverse = true - -[personality.ldu] -columns = ["size"] -only-dirs = true -tree = true -level = 2 -sort = "size" -reverse = true -total-size = true - - -## ── Conditional overrides ───────────────────────────────────────── -## -## Use [[personality.NAME.when]] blocks to override settings based on -## environment variables. Conditions use env.VAR = value, where the -## TOML value type determines the kind of check: -## -## env.VAR = "string" Exact match — variable must equal "string" -## env.VAR = "" Exact match — variable must be set but empty -## env.VAR = true Presence — variable must be set (any value) -## env.VAR = false Absence — variable must be unset -## -## All conditions in a block must match (AND logic). Multiple blocks -## are tried in order; all matching blocks apply (later wins). The -## base personality is the default when no block matches. - -## Example: enable icons only in terminals with Nerd Font support. - -# [[personality.lx.when]] -# env.TERM_PROGRAM = "ghostty" -# icons = "always" - -# [[personality.lx.when]] -# env.TERM_PROGRAM = "WezTerm" -# icons = "always" - -## Example: use a dark theme in a specific terminal. - -# [[personality.lx.when]] -# env.TERM_PROGRAM = "iTerm.app" -# theme = "dracula" - -## Example: disable colour over SSH. - -# [[personality.lx.when]] -# env.SSH_CONNECTION = true -# colour = "never" - - -## ── Themes ──────────────────────────────────────────────────────── -## -## Customise colours for UI elements, file extensions, and filenames. -## Layers on top of LS_COLORS and LX_COLORS (config wins). -## -## Colour values accept: -## Named: "bold blue", "red underline", "dimmed cyan" -## X11/CSS: "tomato", "cornflowerblue", "darkslategray" -## Hex: "#ff8700", "#f00" -## 256-colour: "38;5;208" -## RGB: "38;2;255;135;0" -## Modifiers: bold, dimmed, italic, underline, strikethrough -## -## Themes are selected through personalities (theme = "NAME") or -## the --theme=NAME CLI flag. -## -## Themes can inherit from other themes with `inherits = "NAME"`. -## Without `inherits`, a theme starts from a blank slate. -## -## The special name "exa" refers to the compiled-in default theme. -## It is too long to include here; find it in lxconfig.toml(5). -## -## For the full list of theme keys, also see lxconfig.toml(5). - -# [theme.ocean] -# inherits = "exa" -# directory = "bold dodgerblue" -# executable = "bold springgreen" -# symlink = "mediumturquoise" -# date = "steelblue" -# header = "bold lightsteelblue" -# user-you = "bold paleturquoise" -# vcs-new = "bold mediumspringgreen" -# vcs-modified = "bold lightskyblue" -# vcs-deleted = "bold salmon" -# use-style = "dev" ## apply [style.dev] colours - -# [theme.warm] -# inherits = "exa" -# directory = "bold darkorange" -# executable = "bold chartreuse" -# symlink = "deepskyblue" -# date = "peru" -# header = "bold wheat" -# user-you = "bold goldenrod" -# punctuation = "dimmed sienna" -# use-style = "dev" - -# [theme.minimal] ## no inherits — blank slate -# directory = "bold" -# executable = "green" -# symlink = "dimmed" -# date = "dimmed" -# punctuation = "dimmed" -# header = "bold underline" - - -## ── File-type classes ───────────────────────────────────────────── -## A class is a named list of glob patterns representing a file -## category. Classes are referenced from styles (see below). -## -## The compiled-in classes are: image, video, music, lossless, -## crypto, document, compressed, compiled, temp, immediate. -## For the full definitions, see lxconfig.toml(5). -## -## Override a compiled-in class or define your own: - -# [class] -# source = ["*.rs", "*.py", "*.js", "*.go", "*.c", "*.cpp"] -# data = ["*.csv", "*.json", "*.xml", "*.yaml", "*.h5"] - - -## ── Style sets ─────────────────────────────────────────────────── -## -## Named sets of file-type colour rules. Referenced from themes -## via use-style = "NAME". -## -## Styles can reference classes using bare dotted keys (class.NAME), -## or match files directly using quoted keys ("*.ext" for globs, -## "Makefile" for exact filenames). -## -## Note: all file pattern keys must be quoted. Bare unquoted keys -## are reserved for class references (class.NAME). - -## The "exa" style is the compiled-in default, applied by the "exa" -## theme. Uncomment to customise. -## -## If two classes have overlapping patterns, the result is -## unspecified. The built-in classes have no overlaps. - -# [style.exa] -# class.temp = "38;5;244" -# class.immediate = "bold underline yellow" -# class.image = "38;5;133" -# class.video = "38;5;135" -# class.music = "38;5;92" -# class.lossless = "38;5;93" -# class.crypto = "38;5;109" -# class.document = "38;5;105" -# class.compressed = "red" -# class.compiled = "38;5;137" - -## Custom styles — define your own and reference from a theme: - -# [style.dev] -# class.source = "#ff8700" -# "*.toml" = "sandybrown" -# "*.md" = "cornflowerblue" -# "Makefile" = "bold underline yellow" -# "Cargo.toml" = "bold #ff8700" -# "Dockerfile" = "bold deepskyblue" -# "README.md" = "bold cornflowerblue" diff --git a/private_dot_config/mise/config.toml b/private_dot_config/mise/config.toml.tmpl similarity index 79% rename from private_dot_config/mise/config.toml rename to private_dot_config/mise/config.toml.tmpl index 3320787..ed4c371 100644 --- a/private_dot_config/mise/config.toml +++ b/private_dot_config/mise/config.toml.tmpl @@ -4,12 +4,15 @@ go = "latest" usage = "latest" python = "latest" pnpm = "11" -rust = "1.92.0" +rust = "latest" golangci-lint = "2.7.2" "cargo:jj-gh" = "latest" +"cargo:cargo-binstall" = "latest" +{{- if not .is_work }} "pipx:rendercv" = { version = "latest", extras = "full" } "npm:vit" = "latest" zig = "0.15.2" +{{- end }} [settings] experimental = true diff --git a/private_dot_config/private_fish/completions/http.fish b/private_dot_config/private_fish/completions/http.fish deleted file mode 100644 index 1f27572..0000000 --- a/private_dot_config/private_fish/completions/http.fish +++ /dev/null @@ -1,107 +0,0 @@ -# we have to override vendor completions here - -function __fish_httpie_styles - printf '%s\n' abap algol algol_nu arduino auto autumn borland bw colorful default emacs friendly fruity gruvbox-dark gruvbox-light igor inkpot lovelace manni material monokai murphy native paraiso-dark paraiso-light pastie perldoc pie pie-dark pie-light rainbow_dash rrt sas solarized solarized-dark solarized-light stata stata-dark stata-light tango trac vim vs xcode zenburn -end - -function __fish_httpie_mime_types - test -r /usr/share/mime/types && cat /usr/share/mime/types -end - -function __fish_httpie_print_args - set -l arg (commandline -t) - string match -qe H "$arg" || echo -e $arg"H\trequest headers" - string match -qe B "$arg" || echo -e $arg"B\trequest body" - string match -qe h "$arg" || echo -e $arg"h\tresponse headers" - string match -qe b "$arg" || echo -e $arg"b\tresponse body" - string match -qe m "$arg" || echo -e $arg"m\tresponse metadata" -end - -function __fish_httpie_auth_types - echo -e "basic\tBasic HTTP auth" - echo -e "digest\tDigest HTTP auth" - echo -e "bearer\tBearer HTTP Auth" -end - -function __fish_http_verify_options - echo -e "yes\tEnable cert verification" - echo -e "no\tDisable cert verification" -end - -# Predefined Content Types - -complete -c http -s j -l json -d 'Data items are serialized as a JSON object' -complete -c http -s f -l form -d 'Data items are serialized as form fields' -complete -c http -l multipart -d 'Always sends a multipart/form-data request' -complete -c http -l boundary -x -d 'Custom boundary string for multipart/form-data requests' -complete -c http -l raw -x -d 'Pass raw request data without extra processing' - -# Content Processing Options - -complete -c http -s x -l compress -d 'Content compressed with Deflate algorithm' - -# Output Processing - -complete -c http -l pretty -xa "all colors format none" -d 'Controls output processing' -complete -c http -s s -l style -xa "(__fish_httpie_styles)" -d 'Output coloring style' -complete -c http -l unsorted -d 'Disables all sorting while formatting output' -complete -c http -l sorted -d 'Re-enables all sorting options while formatting output' -complete -c http -l response-charset -x -d 'Override the response encoding' -complete -c http -l response-mime -xa "(__fish_httpie_mime_types)" -d 'Override the response mime type for coloring and formatting' -complete -c http -l format-options -x -d 'Controls output formatting' - -# Output Options - -complete -c http -s p -l print -xa "(__fish_httpie_print_args)" -d 'String specifying what the output should contain' -complete -c http -s h -l headers -d 'Print only the response headers' -complete -c http -s m -l meta -d 'Print only the response metadata' -complete -c http -s b -l body -d 'Print only the response body' -complete -c http -s v -l verbose -d 'Print the whole request as well as the response' -complete -c http -l all -d 'Show any intermediary requests/responses' -complete -c http -s S -l stream -d 'Always stream the response body by line' -complete -c http -s o -l output -F -d 'Save output to FILE' -complete -c http -s d -l download -d 'Download a file' -complete -c http -s c -l continue -d 'Resume an interrupted download' -complete -c http -s q -l quiet -d 'Do not print to stdout or stderr' - -# Sessions - -complete -c http -l session -F -d 'Create, or reuse and update a session' -complete -c http -l session-read-only -F -d 'Create or read a session without updating it' - -# Authentication - -complete -c http -s a -l auth -x -d 'Username and password for authentication' -complete -c http -s A -l auth-type -xa "(__fish_httpie_auth_types)" -d 'The authentication mechanism to be used' -complete -c http -l ignore-netrc -d 'Ignore credentials from .netrc' - -# Network - -complete -c http -l offline -d 'Build the request and print it but don\'t actually send it' -complete -c http -l proxy -x -d 'String mapping protocol to the URL of the proxy' -complete -c http -s F -l follow -d 'Follow 30x Location redirects' -complete -c http -l max-redirects -x -d 'Set maximum number of redirects' -complete -c http -l max-headers -x -d 'Maximum number of response headers to be read before giving up' -complete -c http -l timeout -x -d 'Connection timeout in seconds' -complete -c http -l check-status -d 'Error with non-200 HTTP status code' -complete -c http -l path-as-is -d 'Bypass dot segment URL squashing' -complete -c http -l chunked -d 'Enable streaming via chunked transfer encoding' - -# SSL - -complete -c http -l verify -xa "(__fish_http_verify_options)" -d 'Enable/disable cert verification' -complete -c http -l ssl -x -d 'Desired protocol version to use' -complete -c http -l ciphers -x -d 'String in the OpenSSL cipher list format' -complete -c http -l cert -F -d 'Client side SSL certificate' -complete -c http -l cert-key -F -d 'Private key to use with SSL' -complete -c http -l cert-key-pass -x -d 'Passphrase for the given private key' - -# Troubleshooting - -complete -c http -s I -l ignore-stdin -d 'Do not attempt to read stdin' -complete -c http -l help -d 'Show help' -complete -c http -l manual -d 'Show the full manual' -complete -c http -l version -d 'Show version' -complete -c http -l traceback -d 'Prints exception traceback should one occur' -complete -c http -l default-scheme -x -d 'The default scheme to use' -complete -c http -l debug -d 'Show debugging output' diff --git a/private_dot_config/private_fish/completions/https.fish b/private_dot_config/private_fish/completions/https.fish deleted file mode 100644 index d04133a..0000000 --- a/private_dot_config/private_fish/completions/https.fish +++ /dev/null @@ -1,2 +0,0 @@ -# ditto -complete -c https -w http diff --git a/private_dot_config/private_fish/completions/la.fish b/private_dot_config/private_fish/completions/la.fish deleted file mode 100644 index 2a2cc02..0000000 --- a/private_dot_config/private_fish/completions/la.fish +++ /dev/null @@ -1 +0,0 @@ -complete -c la -w lx diff --git a/private_dot_config/private_fish/completions/ll.fish b/private_dot_config/private_fish/completions/ll.fish deleted file mode 100644 index 73a6761..0000000 --- a/private_dot_config/private_fish/completions/ll.fish +++ /dev/null @@ -1 +0,0 @@ -complete -c ll -w lx diff --git a/private_dot_config/private_fish/completions/lll.fish b/private_dot_config/private_fish/completions/lll.fish deleted file mode 100644 index a3a8fbd..0000000 --- a/private_dot_config/private_fish/completions/lll.fish +++ /dev/null @@ -1 +0,0 @@ -complete -c lll -w lx diff --git a/private_dot_config/private_fish/completions/ls.fish b/private_dot_config/private_fish/completions/ls.fish deleted file mode 100644 index c2be442..0000000 --- a/private_dot_config/private_fish/completions/ls.fish +++ /dev/null @@ -1 +0,0 @@ -complete -c ls -w lx diff --git a/private_dot_config/private_fish/completions/lx.fish b/private_dot_config/private_fish/completions/lx.fish deleted file mode 100644 index 348d452..0000000 --- a/private_dot_config/private_fish/completions/lx.fish +++ /dev/null @@ -1,120 +0,0 @@ -complete -c lx -s L -l level -d 'Limit the depth of recursion' -r -complete -c lx -l classify -d 'Display file kind indicators [always, auto, never]' -r -f -a "always\t'' -auto\t'' -never\t''" -complete -c lx -s I -l ignore -l ignore-glob -d 'Glob patterns (pipe-separated) of files to hide' -r -complete -c lx -l symlinks -d 'How to handle symlinks [show, hide, follow]' -r -f -a "show\t'' -hide\t'' -follow\t''" -complete -c lx -s P -l prune -l prune-glob -d 'Glob patterns of directories to show but not recurse' -r -complete -c lx -s s -l sort -d 'Sort field [name, Name, size, extension, Extension, modified, changed, accessed, created, type, none, inode]' -r -f -a "name\t'' -Name\t'' -size\t'' -extension\t'' -Extension\t'' -modified\t'' -changed\t'' -accessed\t'' -created\t'' -type\t'' -none\t'' -inode\t''" -complete -c lx -l group-dirs -d 'Group directories before or after other files [first, last, none]' -r -f -a "first\t'' -last\t'' -none\t''" -complete -c lx -s p -l personality -d 'Apply a named personality (columns + flags) 🌟 [ll, lll, la, tree, ...]' -r -complete -c lx -l columns -d 'Explicit column list (comma-separated)' -r -complete -c lx -l format -d 'Named column format [long, long2, long3, ...]' -r -f -a "long\t'' -long2\t'' -long3\t''" -complete -c lx -s t -l time -d 'Which timestamp field to display [modified, changed, accessed, created]' -r -f -a "modified\t'' -changed\t'' -accessed\t'' -created\t''" -complete -c lx -l time-style -d 'How to format timestamps [default, iso, long-iso, full-iso, relative, +FORMAT]' -r -complete -c lx -l vcs -d 'VCS backend [auto, git, jj, none]' -r -f -a "auto\t'' -git\t'' -jj\t'' -none\t''" -complete -c lx -l colour -l color -d 'When to use terminal colours [always, auto, never]' -r -f -a "always\t'' -auto\t'' -never\t''" -complete -c lx -l colour-scale -l color-scale -d 'Colour file sizes on a scale [16, 256, none]' -r -f -a "16\t'' -256\t'' -none\t''" -complete -c lx -l icons -d 'Display icons next to file names [always, auto, never]' -r -f -a "always\t'' -auto\t'' -never\t''" -complete -c lx -l theme -d 'Use a named colour theme' -r -complete -c lx -l hyperlink -d 'File names as clickable hyperlinks [always, auto, never]' -r -f -a "always\t'' -auto\t'' -never\t''" -complete -c lx -l quotes -d 'Quote file names containing spaces [always, auto, never]' -r -f -a "always\t'' -auto\t'' -never\t''" -complete -c lx -s w -l width -d 'Override terminal width' -r -complete -c lx -l dump-class -d 'Dump class definitions as TOML' -r -complete -c lx -l dump-format -d 'Dump format definitions as TOML' -r -complete -c lx -l dump-personality -d 'Dump personality definitions as TOML' -r -complete -c lx -l dump-theme -d 'Dump theme definitions as TOML' -r -complete -c lx -l dump-style -d 'Dump style definitions as TOML' -r -complete -c lx -l completions -d 'Generate shell completions [bash, zsh, fish, elvish, powershell]' -r -f -a "bash\t'' -elvish\t'' -fish\t'' -powershell\t'' -zsh\t''" -complete -c lx -s 1 -l oneline -d 'Display one entry per line' -complete -c lx -s l -l long -d 'Long view — repeat for more detail: -ll, -lll' -complete -c lx -s G -l grid -d 'Display entries as a grid (default)' -complete -c lx -s x -l across -d 'Sort the grid across, rather than downwards' -complete -c lx -s R -l recurse -d 'Recurse into directories' -complete -c lx -s T -l tree -d 'Recurse into directories as a tree' -complete -c lx -s C -l count -d 'Print item count to stderr (-CZ includes total size)' -complete -c lx -s b -l binary -d 'File sizes with binary prefixes (KiB, MiB)' -complete -c lx -s B -l bytes -d 'File sizes in bytes, without prefixes' -complete -c lx -s g -l group -d 'Show the group column' -complete -c lx -s n -l numeric -d 'Numeric user and group IDs' -complete -c lx -s h -l header -d 'Add a header row' -complete -c lx -s i -l inode -d 'Show inode numbers' -complete -c lx -s H -l links -d 'Show hard link counts' -complete -c lx -s S -l blocks -d 'Show file system block counts' -complete -c lx -s o -l octal -d 'Show permissions in octal format [aliases: --octal-permissions]' -complete -c lx -s O -l flags -d 'Show file flags (macOS/BSD chflags)' -complete -c lx -s Z -l total-size -d 'Show directory content sizes (recursive)' -complete -c lx -s @ -l extended -d 'Show extended attributes and sizes' -complete -c lx -s a -l all -d 'Show hidden and dot files (-aa for . and ..)' -complete -c lx -s d -l list-dirs -d 'List directories as regular files' -complete -c lx -s D -l only-dirs -d 'List only directories, not files' -complete -c lx -s f -l only-files -d 'List only regular files, not directories' -complete -c lx -s r -l reverse -d 'Reverse the sort order' -complete -c lx -s F -l dirs-first -d 'Directories first [short for --group-dirs=first]' -complete -c lx -s J -l dirs-last -d 'Directories last [short for --group-dirs=last]' -complete -c lx -s m -l modified -d 'Use the modified timestamp' -complete -c lx -s c -l changed -d 'Use the changed timestamp' -complete -c lx -s u -l accessed -d 'Use the accessed timestamp' -complete -c lx -s U -l created -d 'Use the created timestamp' -complete -c lx -l permissions -d 'Show the permissions field' -complete -c lx -l filesize -d 'Show the file size field' -complete -c lx -l user -d 'Show the user field' -complete -c lx -l no-permissions -d 'Suppress the permissions field' -complete -c lx -l no-filesize -d 'Suppress the file size field' -complete -c lx -l no-user -d 'Suppress the user field' -complete -c lx -l no-time -d 'Suppress the time field' -complete -c lx -l no-icons -d 'Suppress icons (alias for --icons=never)' -complete -c lx -l no-inode -complete -c lx -l no-group -complete -c lx -l no-links -complete -c lx -l no-blocks -complete -c lx -l no-octal -complete -c lx -l no-header -complete -c lx -l no-count -complete -c lx -l no-total-size -complete -c lx -l vcs-status -d 'Show per-file VCS status column' -complete -c lx -l vcs-ignore -d 'Hide VCS-ignored files and metadata directories' -complete -c lx -l vcs-repos -d 'Show per-directory VCS repo indicator' -complete -c lx -s A -l absolute -d 'Show absolute file paths' -complete -c lx -l show-config -d 'Show the active configuration and exit' -complete -c lx -l init-config -d 'Generate a default config file' -complete -c lx -l upgrade-config -d 'Upgrade a legacy config file' -complete -c lx -s ? -l help -d 'Print help information' -complete -c lx -s v -l version -d 'Print version information' diff --git a/private_dot_config/private_fish/completions/tree.fish b/private_dot_config/private_fish/completions/tree.fish deleted file mode 100644 index 1072178..0000000 --- a/private_dot_config/private_fish/completions/tree.fish +++ /dev/null @@ -1 +0,0 @@ -complete -c tree -w lx diff --git a/private_dot_config/private_fish/config.fish b/private_dot_config/private_fish/config.fish index 2c257e1..05aab65 100644 --- a/private_dot_config/private_fish/config.fish +++ b/private_dot_config/private_fish/config.fish @@ -7,16 +7,29 @@ if status is-interactive end # eval "$(atuin hex init)" -#zoxide init fish | source +# zoxide is set up by the zoxide.fish plugin + +set --export EXA_COLORS "da=1;34" +set --export BAT_THEME "Monokai Extended Origin" +set --export GOPATH "$HOME/go" +set --export CLAUDE_CODE_PLUGIN_PREFER_HTTPS 1 + +# The following lines were added by Docker Desktop to add commands to your PATH. +set -gx PATH $PATH "$HOME/.docker/bin" +# End of Docker Desktop section. + # pnpm -# set -gx PNPM_HOME "/Users/jack/Library/pnpm" -# if not string match -q -- $PNPM_HOME $PATH -# set -gx PATH "$PNPM_HOME" $PATH -# end +set -gx PNPM_HOME "$HOME/Library/pnpm" +if not string match -q -- $PNPM_HOME $PATH + set -gx PATH "$PNPM_HOME" $PATH +end # pnpm end +test -f ~/.config/op/plugins.sh; and source ~/.config/op/plugins.sh + scheme set monokai ~/.local/bin/mise activate fish | source +direnv hook fish | source set -g fish_transient_prompt 1 fish_default_key_bindings @@ -27,11 +40,10 @@ set -g tide_jj_show_description false source ~/.orbstack/shell/init2.fish 2>/dev/null || : # Added by LM Studio CLI (lms) -set -gx PATH $PATH /Users/jack/.lmstudio/bin +set -gx PATH $PATH "$HOME/.lmstudio/bin" # End of LM Studio CLI section projj shell-setup fish | source # fish if status is-interactive atuin init fish | source end - diff --git a/private_dot_config/private_fish/fish_plugins b/private_dot_config/private_fish/fish_plugins index 8fd7902..d2edcb3 100644 --- a/private_dot_config/private_fish/fish_plugins +++ b/private_dot_config/private_fish/fish_plugins @@ -1,11 +1,9 @@ jorgebucaran/fisher -wfxr/forgit nickeb96/puffer-fish h-matsuo/fish-color-scheme-switcher halostatue/fish-chezmoi@v1 ilancosman/clownfish icezyclon/zoxide.fish -scaryrawr/copilot.fish jorgebucaran/fishtape nertzy/fish_jj_prompt patrickf1/fzf.fish diff --git a/private_dot_ssh/private_config.tmpl b/private_dot_ssh/private_config.tmpl index 785c775..35e517e 100644 --- a/private_dot_ssh/private_config.tmpl +++ b/private_dot_ssh/private_config.tmpl @@ -6,3 +6,9 @@ Include ~/.ssh/config.d/* Host unifi SetEnv TERM=xterm-256color + +Host * + AddKeysToAgent yes +{{- if eq .chezmoi.os "darwin" }} + UseKeychain yes +{{- end }} -- 2.51.2 From 495961e3754f659ff9832a82265be8593afc1888 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Wed, 7 Oct 2026 08:45:22 -0700 Subject: [PATCH 03/16] Add htop config and mock/opr fish functions Co-Authored-By: Claude Opus 5.5 --- .../private_fish/functions/mock.fish | 81 +++++++++++++++++++ .../private_fish/functions/opr.fish | 3 + private_dot_config/private_htop/htoprc | 53 ++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 private_dot_config/private_fish/functions/mock.fish create mode 100644 private_dot_config/private_fish/functions/opr.fish create mode 100644 private_dot_config/private_htop/htoprc diff --git a/private_dot_config/private_fish/functions/mock.fish b/private_dot_config/private_fish/functions/mock.fish new file mode 100644 index 0000000..87f6592 --- /dev/null +++ b/private_dot_config/private_fish/functions/mock.fish @@ -0,0 +1,81 @@ +function mock + argparse --stop-nonopt e/erase h/help -- $argv + set -l cmd $argv[1] + set -l arg $argv[2] + set -l executedCode $argv[3] + + if set -q _flag_help + _mock_help + else if set -q _flag_erase + if test -z "$arg" + functions --erase (functions --all | string match --regex '^_mock_'$cmd'_.*') + functions --erase $cmd + functions --copy _non_mocked_$cmd $cmd 2>/dev/null # Copy _non_mocked_$cmd -> $cmd if it exists + else + functions --erase _mock_"$cmd"_"$arg" + end + else + set -l type (type --type $cmd 2>/dev/null) # If $cmd doesn't exist, don't error + + # If $cmd isn't a function, or if _non_mocked_$cmd is already defined, don't error + functions --copy $cmd _non_mocked_$cmd 2>/dev/null + + function _mock_"$cmd"_"$arg" --inherit-variable arg --inherit-variable executedCode + set -l argv (string replace -- "$arg " '' "$argv") + + eval $executedCode + end + + function $cmd --inherit-variable cmd --inherit-variable type + # This is looking from most-specific to least specific mock + # For example, if we have mocked "foo bar" AND "foo bar baz", + # if we run "foo bar baz" then it will run that mock, + # as opposed to the more general "foo bar" + set -l i (count $argv) + while test $i -gt 0 + set -l argument "$argv[1..$i]" + if functions --query _mock_"$cmd"_"$argument" + _mock_"$cmd"_"$argument" $argv + return + end + set i (math $i-1) + end + + if functions --query _mock_"$cmd"_\* + _mock_"$cmd"_\* $argv + else + switch $type + case function + _non_mocked_$cmd $argv + case builtin + builtin $cmd $argv + case file + command $cmd $argv + end + end + end + end +end + +function _mock_help + printf '%s' ' +Usage: mock [options] [command] [argument] [executed code] + +Options: + -e or --erase erase a mocked command/argument + -h or --help print this help message + +Examples: + mock git pull "echo This command echoes succesfully" + mock git push "echo This command fails with status 1; return 1" + mock git \* "echo This command acts as a fallback to all git commands" + + mock -e git push # Removes git push mock + mock -e git \* # Removes the fallback mock + mock -e git # Removes all git mocks + +Tips: + - Many mocks can be applied to the same command at the same time with different arguments. + - Be sure to escape the asterisk symbol when using it as a fallback (\*). +' +end diff --git a/private_dot_config/private_fish/functions/opr.fish b/private_dot_config/private_fish/functions/opr.fish new file mode 100644 index 0000000..30ac7e6 --- /dev/null +++ b/private_dot_config/private_fish/functions/opr.fish @@ -0,0 +1,3 @@ +function opr + op run --env-file=.env -- $argv +end \ No newline at end of file diff --git a/private_dot_config/private_htop/htoprc b/private_dot_config/private_htop/htoprc new file mode 100644 index 0000000..4220e42 --- /dev/null +++ b/private_dot_config/private_htop/htoprc @@ -0,0 +1,53 @@ +# Beware! This file is rewritten by htop when settings are changed in the interface. +# The parser is also very primitive, and not human-friendly. +htop_version=3.2.2 +config_reader_min_version=3 +fields=0 48 17 18 38 39 2 46 47 49 1 +hide_kernel_threads=1 +hide_userland_threads=0 +hide_running_in_container=0 +shadow_other_users=0 +show_thread_names=0 +show_program_path=1 +highlight_base_name=0 +highlight_deleted_exe=1 +shadow_distribution_path_prefix=0 +highlight_megabytes=1 +highlight_threads=1 +highlight_changes=0 +highlight_changes_delay_secs=5 +find_comm_in_cmdline=1 +strip_exe_from_cmdline=1 +show_merged_command=0 +header_margin=1 +screen_tabs=1 +detailed_cpu_time=0 +cpu_count_from_one=0 +show_cpu_usage=1 +show_cpu_frequency=0 +update_process_names=0 +account_guest_in_cpu_meter=0 +color_scheme=0 +enable_mouse=1 +delay=15 +hide_function_bar=0 +header_layout=two_50_50 +column_meters_0=LeftCPUs2 Memory Swap +column_meter_modes_0=1 1 1 +column_meters_1=RightCPUs2 Tasks LoadAverage Uptime +column_meter_modes_1=1 2 2 2 +tree_view=0 +sort_key=46 +tree_sort_key=0 +sort_direction=-1 +tree_sort_direction=1 +tree_view_always_by_pid=0 +all_branches_collapsed=0 +screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command +.sort_key=PERCENT_CPU +.tree_sort_key=PID +.tree_view=0 +.tree_view_always_by_pid=0 +.sort_direction=-1 +.tree_sort_direction=1 +.all_branches_collapsed=0 -- 2.51.2 From 7e5d7e5646bc52ea8fce48ac68c114536cb9e194 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Wed, 7 Oct 2026 08:50:40 -0700 Subject: [PATCH 04/16] Split CLAUDE.md into shared rules and an encrypted work profile Co-Authored-By: Claude Opus 5.5 --- .chezmoiignore | 1 + dot_claude/CLAUDE.md.tmpl | 36 ++++++++++++++++++++++++++++++++ dot_claude/encrypted_work.md.age | 22 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 dot_claude/CLAUDE.md.tmpl create mode 100644 dot_claude/encrypted_work.md.age diff --git a/.chezmoiignore b/.chezmoiignore index 69df27e..5557208 100644 --- a/.chezmoiignore +++ b/.chezmoiignore @@ -12,4 +12,5 @@ README.md .config/git/work.conf .config/jj/conf.d .config/fish/conf.d/work.fish +.claude/work.md {{ end }} diff --git a/dot_claude/CLAUDE.md.tmpl b/dot_claude/CLAUDE.md.tmpl new file mode 100644 index 0000000..795661a --- /dev/null +++ b/dot_claude/CLAUDE.md.tmpl @@ -0,0 +1,36 @@ +## Writing Rules + +- American, not British/Canadian. `behavior` not `behaviour`, `normalize` not `normalise`, `centered` not `centred`, `labeled` not `labelled`. +- Applies everywhere: chat, code comments, docstrings, commit messages, docs. +- When normalizing an existing codebase, only substitute British → American, never the reverse. Protect wire-format keys (e.g. 1PUX has a `capitalization` field), identifiers from external data, and quoted error strings — then confirm the diff touched no code lines. + +Writing rules, from Orwell, 1946. These govern prose: docs, PR text, messages. Never touch code or technical terms; swap in everyday words only where precision survives. + +1. Never use a metaphor, simile or other figure of speech which you are used to seeing in print. +2. Never use a long word where a short one will do. +3. If it is possible to cut a word out, always cut it out. +4. Never use the passive where you can use the active. +5. Never use a foreign phrase, a scientific word or a jargon word if you can think of an everyday English equivalent. +6. Break any of these rules sooner than say anything outright barbarous. + Review every prose output against these rules before delivering. + +Report progress in plain sentences: what changed, what failed, what comes next. No emoji checkmarks, no Successfully, no Perfect, no wall of bullets. Start with three lines; add detail only when it changes the next action. + +## Tooling + +- Difftastic is set as `diff.external` **intentionally** — don't suggest unsetting it. It only affects **patch text**, so pass `--no-ext-diff` (with `--no-pager`) on `git diff` / `show` / `log -p` whenever the patch will be parsed, grepped, or compared. Without it, output is side-by-side and `grep -E '^[+-]'` matches nothing — silently reading as "no differences" when files do differ. +- Unaffected, safe without the flag: `--stat`, `--numstat`, `--shortstat`, `--name-only`, `--name-status`, `--raw`, and `--quiet` / `--exit-code` exit codes. Prefer these when a summary suffices, and use them to cross-check whenever a patch-derived grep comes back empty. +- `commit.gpgsign` is on with an SSH key via agent: a `git commit` that appears to hang is likely waiting on an approval prompt, not stuck. Surface it rather than retrying. +- If the `jujutsu-workflow-skill` suggests using `ui.paginate never --user` to make the ui safe to use for an agent, do not run that command. That command will leak out of the agent scope and disable the pager for the entire user. Lean on `--no-pager` instead. + +## 1Password CLI + +- Every `op` call from the Bash tool costs me a biometric approval — there's no session reuse between calls. Never run `op` speculatively, to explore, or to test a theory; batch everything needed into a single invocation. +- Exit codes say nothing about whether a prompt fired. The approval happens out of band, where you can't see it. Don't conclude from a fast exit 0 that it was free. +- Interactive `op` subcommands can't work here at all (no tty) — hand those to me as `! op ...`. +- `op whoami` printing `account is not signed in` is the normal starting state, not a bug; use `op vault list` as a health check. `op signin` returning exit 0 with no output is success. +- `OP_SERVICE_ACCOUNT_TOKEN` in the launching environment is the only thing that skips the prompt. Ask before setting it up — it's a long-lived token in plaintext on disk. +{{- if .is_work }} + +@~/.claude/work.md +{{- end }} diff --git a/dot_claude/encrypted_work.md.age b/dot_claude/encrypted_work.md.age new file mode 100644 index 0000000..56e56bd --- /dev/null +++ b/dot_claude/encrypted_work.md.age @@ -0,0 +1,22 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHanZnWFN5STNaQkoyM3NF +dC91dHRYMVFjdmtKZ3JoMXNKWTZkL1hjRWpRCjhHSU42emN0NTZqQzlNTGViMG1j +TDBHbTdhNFRkdHRPb1grQS94VFNzQ1UKLS0tIGRZRnIyajZpdmdEVGVPcFdOdlcy +VC9MYW1wN3R1aUdadkR4RTJEZmVtYW8KwCbtQgVnmEeKwFen3eHBFd13A5U38D0k +g1Iy/en4q7ZK62S8PA21td8zfTUU8yRsyg/7gT4wgeOqcrRRsBBYS4uk9RRYchPM +kJ2AWI115cMCx+lL6O8vGHL/d0Qp2gAw3S4H6ZFRHSS2JPxE3FKpCLbQXI0+/NhW +tgG3XBAuRlpL4dekeLiPsyKa2tfmeTOZhDxUgtIvqTcH2PfKQdN6xOSg5mqLWyM7 +bTZHk3AHH9hyzX2mJRvaITsxxPHfNv4P1euqtnwpre3aHCBy52wpe5xbtpqLYWq2 +6Vh91FjDDtlXLLDyS+yIGQL4dcMk/2XDB/psynmwXNKHVwJ20qx5A/WPL8wkX6a3 +X3XfusJCU47NWY2S241fje9DDwiLEspjnLEUMMrAfVjdnNRLFKiYJneK0fQgCZxo +zFLb8KICcOGAbGOd9bi7X1c0fR/yNccDZSEP+5MBK1fqMODpOCoLJ/MmNeqwa0T2 +UfJansjCF5//0cuvbss3Drm5lnbXIRkE8fTUn8OOGRdKnukG2tl0Bt28xgRIEJSe +1p25BkbelEjE9T35uKfUc5wo1LWFLBjn5ZGc6fFPSHhvXIC5FfRGPIIO205xzfH9 +AFdrwpl6OeYHtkNw5OyetUoSptW7/39aje4ZzriKQDvZJ+2qQvO09Ka7w+zs92Yv +9A7o5feEVOXqGdt+ldjCmt2K+ggd4MyCVKmxAc5FTSStiZcqzHvdsSfkqDpbAJR7 +YChMV+X0iT4CrmJs1TC8KKOf9z84PKj1zbLYDQ2xAc2nkS0E5BqMQ6wis4Kbc4MT +3NfZuJpPpaGtIWatLszRuD3V5NAyaIz9dj+B0h3myq9mMawoIItubZ2MPnbxpBFp +/lEp1E7wCMVeAxZ5O/Lpz0ank6NhZE4S4+DyOKG3nBt4bnXEJvBEZcIQ9UvqyT9T +SlcAF7yYddHzoxRAChhIi/rO2CT7Wj8F84WYqlbZX/seMBXAeX8XfnotboH3GgFH +hihJ8FeQZlImdgoiAIUrcU7SRbhVyr2eiA== +-----END AGE ENCRYPTED FILE----- -- 2.51.2 From 6d4ef049a3d0cdfd7492f214cb81473ff8731d47 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Wed, 7 Oct 2026 08:50:53 -0700 Subject: [PATCH 05/16] Update .chezmoiignore Add .config/1Password/ssh/agent.toml --- .chezmoiignore | 1 + .../encrypted_private_agent.toml.age | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 private_dot_config/private_1Password/private_ssh/encrypted_private_agent.toml.age diff --git a/.chezmoiignore b/.chezmoiignore index 5557208..b5dec43 100644 --- a/.chezmoiignore +++ b/.chezmoiignore @@ -13,4 +13,5 @@ README.md .config/jj/conf.d .config/fish/conf.d/work.fish .claude/work.md +.config/1Password {{ end }} diff --git a/private_dot_config/private_1Password/private_ssh/encrypted_private_agent.toml.age b/private_dot_config/private_1Password/private_ssh/encrypted_private_agent.toml.age new file mode 100644 index 0000000..2398675 --- /dev/null +++ b/private_dot_config/private_1Password/private_ssh/encrypted_private_agent.toml.age @@ -0,0 +1,29 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxS0RldGE4d3pORStuem9V +S1lsUHZhenNWVW9zQU5zSHJEeS81dUl1S0hNCmM5b244NGFmaFo4SUF3alBsZTRs +Z3dCMFF1OVQrTXhLMGd1aS96U3hkcVEKLS0tIDBTWExCQ1gxYUVsY0VObXlPMnJQ +L013dGo2Tzh0YlBjVkp1K2dSMFkxVEUKkdbX+2tfk9EK7hZvWBoEtV4012rmb0az +5S7LKNFA0NetuBy91YqHZIo/WTH5meqFTIoJF7hmapghJV6vY6/PYrV+M359ec6E +d/4oKRNCjYqpKSf4Qlke2R6enC0aAjYj9Xflrx39xmAv6XMNkFF08L80/gICCwRw +8BOsQsPNIuypCzdSFTHqOoyLG0ZiBD+kfY3NI/ej/hz2QgUAuIgBYCFbCt2VI/Ee +hcj5LcDbjVZabI0kX7ln/IlYU6JEj8bf1QavuKTw57iMQ/IUnWrNMmy4idlaqAFi +J88TLeiVL6zW+iBL+6zDdQFgDzYPSbUPvcv1sbX8s475fza2OL1fZFHy0yc6N8sg +n88+zU8qk5kW1XhTp7VmhnSa3hVHr6psyhD+dFHapWyqTsaVSMFcChbo9zOEhBOi +3TTVdwFELuU6BmF+f9owsCXzhqDIpYDjC1hmsgkzA2v8yYRhvQWDkoY0cBU13yCk +p2y0krfZTfyRQE9tesxmiBTSgI2TLfqYt+7M9l4y35oXkcF8rSZrFjhj+QUGyW+5 +lOuIZWbjgbHXtAwQfks0Obn/Hqj44FHdFlhJRUAvj/Va6UhofSG/eYrFw/M0I1ke ++x9AwnPNpgipKBKWlmKRu+/74xWBDnhvz6E3ITOm00bEG9BujDLYneOWR9AXSFBc +3S7yJz8HpaWtwy6ZbOyBFHvPIm6gHCRVHo/gs3pEkLAbeUzTvYECp9NqofPgGwWl +EZw5hhXPQxr8tQO0h58g1wi2QHYXQi4Fe+08/aYxQCzguZAODDZnnfSPnlxgrIjX +yzMqM+SsXOE0d85bQKDxFPLymRhLSA7mx/wtC0+hAkw1J8yljczWgup681Zo/pUZ +Dl0kvj/fBamdEpbxPlRYPVkuSDHYc58o7WyJeE5eYO/1PNUmAb8ymfZkPoAdnQv9 +H6ASaMovL/fVED0Xj96WsxY6vDk3Bn0n9bAbpmDiJiSL8n91YaijDy3qm0z89HLs +DjBP+oNQWbBl120uln5IeuzyHyYnPr00ihMHXXZ1WbuycVqvlZM+aPzAXWQ+b5wi +w1NvfyWWRVlkAqezkRWR2iyP3yimUa9h7W/85hFZLbTdbWCObszsA7k9hNyP9kOY +WGfQJhcBRMc2yADhu1M9nNFWCAfsF0BnhXuF3fJ0Ar8V4tekaG/4LRMr7nAVyNpe +ddDFL2ERMnDMY4GPYgtAwEOAUjWMFjWSjkqHQfiIL+9JrDgE7ZqzAwem4gZYonLA +EjbSahdQHphxq/WVEicvubc5luBjRPkwHdkEL8ytNpGkQ2hfhSsW6yuZxgp4mcLP +CHHYCK9E5IjM/BsJRTBC7YIbXrEEOogBhqziXO9Azj5Z52jINnoqHgzpB0RUYYuj +8X2JW5bsneokWHMPgFkILd/0gP509BlotSSJVHNfSW9y4MBFqXPepZs8eHjN73yV +OLbZw6y0OHWmn+NUcXn2cTaWa3xeIrkI5Rnf +-----END AGE ENCRYPTED FILE----- -- 2.51.2 From 0bf6e6e98c8c7688f4e6914e8bcc230a851e2a5c Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Wed, 7 Oct 2026 09:22:27 -0700 Subject: [PATCH 06/16] Add brew-drift and Brewfile install hook - brew-drift sorts installed-but-untracked packages into a shared or work Brewfile, and handles tracked packages that aren't installed - Install missing Brewfile entries on apply, without upgrading - Brewfiles stay in the source directory and are not deployed Co-Authored-By: Claude Opus 5.5 --- .chezmoiignore | 2 + Brewfile | 0 Brewfile.work | 0 private_dot_local/bin/executable_brew-drift | 138 ++++++++++++++++++++ run_onchange_after_brew-bundle.sh.tmpl | 10 ++ 5 files changed, 150 insertions(+) create mode 100644 Brewfile create mode 100644 Brewfile.work create mode 100644 private_dot_local/bin/executable_brew-drift create mode 100644 run_onchange_after_brew-bundle.sh.tmpl diff --git a/.chezmoiignore b/.chezmoiignore index b5dec43..221042f 100644 --- a/.chezmoiignore +++ b/.chezmoiignore @@ -2,6 +2,8 @@ install.sh LICENSE README.md +Brewfile +Brewfile.work {{ if .is_work }} .config/halloy {{ else }} diff --git a/Brewfile b/Brewfile new file mode 100644 index 0000000..e69de29 diff --git a/Brewfile.work b/Brewfile.work new file mode 100644 index 0000000..e69de29 diff --git a/private_dot_local/bin/executable_brew-drift b/private_dot_local/bin/executable_brew-drift new file mode 100644 index 0000000..395af75 --- /dev/null +++ b/private_dot_local/bin/executable_brew-drift @@ -0,0 +1,138 @@ +#!/usr/bin/env fish +# Sort Homebrew drift into the chezmoi Brewfiles. +# +# Installed but untracked: track as shared or work, uninstall, or skip. +# Tracked but not installed: install, remove from the Brewfile, or skip. + +set -x HOMEBREW_NO_AUTO_UPDATE 1 +set src (chezmoi source-path); or exit 1 +set shared $src/Brewfile +set work $src/Brewfile.work +set is_work (chezmoi execute-template '{{ .is_work }}') +touch $shared $work + +# Print "typenameentry linedescription" for each tap/brew/cask. +function entries + set -l desc '' + while read -l line + if string match -q '# *' -- $line + set desc (string sub -s 3 -- $line) + else if set -l m (string match -rg '^(tap|brew|cask) "([^"]+)"' -- $line) + printf '%s\t%s\t%s\t%s\n' $m[1] $m[2] $line $desc + set desc '' + end + end +end + +function key + string split -f1,2 \t -- $argv[1] | string join \t +end + +# Taps go first so their trust settings apply before anything that needs them. +function track --argument-names item file + set -l f (string split \t -- $item) + set -l block + test -n "$f[4]"; and set block "# $f[4]" + set -a block $f[3] + if test $f[1] = tap + printf '%s\n' $block (cat $file) >$file.tmp; and mv $file.tmp $file + else + printf '%s\n' $block >>$file + end +end + +# Edit the text directly: brew bundle remove fails on formulae that no longer +# exist, or leaves the description behind. +function untrack --argument-names item + set -l k (key $item) + for file in $shared $work + set -l out + for line in (cat $file) + set -l m (string match -rg '^(tap|brew|cask) "([^"]+)"' -- $line) + if test (count $m) -eq 2; and test "$m[1]"\t"$m[2]" = "$k" + string match -q '# *' -- "$out[-1]"; and set -e out[-1] + else + set -a out $line + end + end + printf '%s\n' $out >$file + end +end + +function pick --argument-names header + fzf --multi --delimiter \t --with-nth 1,2,4 --header $header \ + --with-shell 'fish -c' \ + --preview 'switch {1}; case tap; brew tap-info {2}; case brew; brew info --formula {2}; case cask; brew info --cask {2}; end' \ + --preview-window right,60%,wrap +end + +function choose --argument-names header + printf '%s\n' $argv[2..] | fzf --header $header --height 12 --reverse +end + +function confirm --argument-names prompt + read -l -P "$prompt [y/N] " answer + string match -qi y -- $answer +end + +echo "Reading installed packages and Brewfiles..." +set installed (brew bundle dump --file=- --tap --formula --cask 2>/dev/null | entries) +set files $shared +test "$is_work" = true; and set -a files $work +set tracked (cat $files | entries) + +set installed_keys (for i in $installed; key $i; end) +set tracked_keys (for t in $tracked; key $t; end) +set untracked (for i in $installed; contains -- (key $i) $tracked_keys; or echo $i; end) +set missing (for t in $tracked; contains -- (key $t) $installed_keys; or echo $t; end) + +set actions shared skip uninstall +test "$is_work" = true; and set actions shared work skip uninstall + +while test (count $untracked) -gt 0 + set picked (printf '%s\n' $untracked | pick (count $untracked)" installed but untracked. Tab selects, Enter confirms, Esc moves on."); or break + set action (choose "Track or remove "(count $picked)" item(s)?" $actions); or continue + if test $action = uninstall + printf ' %s\n' $picked | cut -f1,2 + confirm "Uninstall these?"; or continue + end + for p in $picked + set f (string split \t -- $p) + switch $action + case shared + track $p $shared + case work + track $p $work + case uninstall + switch $f[1] + case tap + brew untap $f[2] + case cask + brew uninstall --cask $f[2] + case brew + brew uninstall --formula $f[2] + end + end + set -e untracked[(contains -i -- $p $untracked)] + end +end + +while test (count $missing) -gt 0 + set picked (printf '%s\n' $missing | pick (count $missing)" in a Brewfile but not installed. Tab selects, Enter confirms, Esc moves on."); or break + set action (choose "What to do with "(count $picked)" item(s)?" install "remove from Brewfile" skip); or continue + for p in $picked + set f (string split \t -- $p) + switch $action + case install + echo $f[3] | brew bundle install --no-upgrade --file=- + case "remove from Brewfile" + untrack $p + end + set -e missing[(contains -i -- $p $missing)] + end +end + +echo +git -C $src --no-pager diff --stat -- Brewfile Brewfile.work +git -C $src status --short -- Brewfile Brewfile.work +echo "Commit with: chezmoi git -- add Brewfile Brewfile.work; and chezmoi git -- commit -m 'Update Brewfiles'" diff --git a/run_onchange_after_brew-bundle.sh.tmpl b/run_onchange_after_brew-bundle.sh.tmpl new file mode 100644 index 0000000..5a6414e --- /dev/null +++ b/run_onchange_after_brew-bundle.sh.tmpl @@ -0,0 +1,10 @@ +{{ if eq .chezmoi.os "darwin" -}} +#!/bin/sh +# Install anything new in the Brewfiles. Sort drift back into them with brew-drift. +# Brewfile hash: {{ include "Brewfile" | sha256sum }} +{{- if .is_work }} +# Brewfile.work hash: {{ include "Brewfile.work" | sha256sum }} +{{- end }} +set -eu +cat {{ joinPath .chezmoi.sourceDir "Brewfile" | quote }}{{ if .is_work }} {{ joinPath .chezmoi.sourceDir "Brewfile.work" | quote }}{{ end }} | brew bundle install --no-upgrade --file=- +{{ end -}} -- 2.51.2 From 32a256afd7a8fc9054d9bdf1edcd971713baaba1 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Wed, 7 Oct 2026 10:01:55 -0700 Subject: [PATCH 07/16] brew-drift: list top-down and track taps with their packages - Show the picker top to bottom instead of fzf's bottom-up default - Tracking a package from a third-party tap also tracks the tap, with its options, in the same file; a shared tap leaves Brewfile.work - Leave a Brewfile empty, not a blank line, when its last entry goes Co-Authored-By: Claude Opus 5.5 --- private_dot_local/bin/executable_brew-drift | 42 ++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/private_dot_local/bin/executable_brew-drift b/private_dot_local/bin/executable_brew-drift index 395af75..4c3c081 100644 --- a/private_dot_local/bin/executable_brew-drift +++ b/private_dot_local/bin/executable_brew-drift @@ -28,9 +28,28 @@ function key string split -f1,2 \t -- $argv[1] | string join \t end +function in_file --argument-names k file + contains -- $k (cat $file | entries | while read -l e; key $e; end) +end + +function forget_untracked --argument-names item + set -l i (contains -i -- $item $untracked); and set -e untracked[$i] +end + # Taps go first so their trust settings apply before anything that needs them. +# Tracking a package from a third-party tap also tracks the tap in the same +# file. Shared covers work machines, so a shared tap leaves Brewfile.work. function track --argument-names item file set -l f (string split \t -- $item) + set -l k (key $item) + if test $f[1] = tap + if test $file = $shared; and in_file $k $work + untrack $item $work + else if test $file = $work; and in_file $k $shared + return + end + end + in_file $k $file; and return set -l block test -n "$f[4]"; and set block "# $f[4]" set -a block $f[3] @@ -39,13 +58,22 @@ function track --argument-names item file else printf '%s\n' $block >>$file end + set -l tap (string match -rg '^([^/]+/[^/]+)/' -- $f[2]) + if test -n "$tap"; and not string match -q 'homebrew/*' -- $tap + set -l tap_item (for i in $installed; test (key $i) = "tap"\t"$tap"; and echo $i; end) + test -n "$tap_item"; or set tap_item "tap"\t"$tap"\t"tap \"$tap\""\t + track $tap_item $file + forget_untracked $tap_item + end end # Edit the text directly: brew bundle remove fails on formulae that no longer # exist, or leaves the description behind. function untrack --argument-names item set -l k (key $item) - for file in $shared $work + set -l targets $argv[2..] + test (count $targets) -gt 0; or set targets $shared $work + for file in $targets set -l out for line in (cat $file) set -l m (string match -rg '^(tap|brew|cask) "([^"]+)"' -- $line) @@ -55,12 +83,16 @@ function untrack --argument-names item set -a out $line end end - printf '%s\n' $out >$file + if set -q out[1] + printf '%s\n' $out >$file + else + : >$file + end end end function pick --argument-names header - fzf --multi --delimiter \t --with-nth 1,2,4 --header $header \ + fzf --multi --reverse --delimiter \t --with-nth 1,2,4 --header $header \ --with-shell 'fish -c' \ --preview 'switch {1}; case tap; brew tap-info {2}; case brew; brew info --formula {2}; case cask; brew info --cask {2}; end' \ --preview-window right,60%,wrap @@ -113,7 +145,7 @@ while test (count $untracked) -gt 0 brew uninstall --formula $f[2] end end - set -e untracked[(contains -i -- $p $untracked)] + forget_untracked $p end end @@ -128,7 +160,7 @@ while test (count $missing) -gt 0 case "remove from Brewfile" untrack $p end - set -e missing[(contains -i -- $p $missing)] + set -l i (contains -i -- $p $missing); and set -e missing[$i] end end -- 2.51.2 From d4c1387fe73ba1899efda91d7f3adfc5d73c1f83 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 12:12:44 -0700 Subject: [PATCH 08/16] Drop delta from git config; difftastic handles diffs Co-Authored-By: Claude Opus 5.5 --- private_dot_config/git/config.tmpl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/private_dot_config/git/config.tmpl b/private_dot_config/git/config.tmpl index 4d9f321..c847e77 100644 --- a/private_dot_config/git/config.tmpl +++ b/private_dot_config/git/config.tmpl @@ -5,9 +5,6 @@ [init] defaultBranch = main -[include] - path = {{ joinPath .chezmoi.homeDir ".local/share/delta/themes.gitconfig" | quote }} - [user] name = "{{ .name }}" {{- if .is_work }} @@ -85,14 +82,6 @@ [core] editor = nano excludesfile = {{joinPath .chezmoi.homeDir ".config" "git" "ignore" | quote}} -[interactive] - diffFilter = delta --color-only - -[delta] - features = woolly-mammoth - navigate = true - light = false - [merge] conflictstyle = diff3 tool = kale -- 2.51.2 From 4d2685efce1ffaab70dcec62dee7891584aa6f54 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 12:12:50 -0700 Subject: [PATCH 09/16] Update Brewfile Update Brewfile.work Update .config/jj/config.toml --- Brewfile | 112 +++++++++++++++++++++++++ Brewfile.work | 16 ++++ private_dot_config/jj/config.toml.tmpl | 2 - 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/Brewfile b/Brewfile index e69de29..fe686da 100644 --- a/Brewfile +++ b/Brewfile @@ -0,0 +1,112 @@ +tap "popomore/tap" +# Record and share terminal sessions +brew "asciinema" +# Code searching, linting, rewriting +brew "ast-grep" +# Improved shell history for zsh, bash, fish and nushell +brew "atuin", restart_service: :changed +# Clone of cat(1) with syntax highlighting and Git integration +brew "bat" +# Manage your dotfiles across multiple diverse machines, securely +brew "chezmoi" +# Diff that understands syntax +brew "difftastic" +# Synchronize your DNS to multiple providers from a simple DSL +brew "dnscontrol" +# Load/unload environment variables based on $PWD +brew "direnv" +# Modern, maintained replacement for ls +brew "eza" +# Like neofetch, but much faster because written mostly in C +brew "fastfetch" +# Simple, fast and user-friendly alternative to find +brew "fd" +# User-friendly command-line shell for UNIX-like operating systems +brew "fish" +# Command-line fuzzy finder written in Go +brew "fzf" +# Distributed revision control system +brew "git" +# Improved top (interactive process viewer) +brew "htop" +# Git-compatible distributed version control system +brew "jj" +# Lightweight and flexible command-line JSON processor +brew "jq" +# TUI for interacting with the Jujutsu version control system +brew "jjui" +# Handy way to save and run project-specific commands +brew "just" +# Free (GNU) replacement for the Pico text editor +brew "nano" +# Statically typed programming language for scientific computations +brew "numbat" +# Command-line Git information tool +brew "onefetch" +# General purpose AT Protocol CLI in Go +brew "goat" +# Command-line DNS Client for Humans +brew "doggo" +# Render markdown on the CLI +brew "glow" +# GNU Privacy Guard (OpenPGP) +brew "gnupg" +# Post-modern modal text editor +brew "helix" +# Kubernetes package manager +brew "helm" +# Command-line hex viewer +brew "hexyl" +# Modern and intuitive terminal-based text editor +brew "micro" +# Nice to use pager for humans +brew "moor" +# Drop-in replacement for Terraform. Infrastructure as Code Tool +brew "opentofu" +# Feature-rich terminal-based text viewer +brew "ov" +# Search tool like grep and The Silver Searcher +brew "ripgrep" +# Fuzzy Finder in rust! +brew "sk" +# Cross-shell prompt for astronauts +brew "starship" +# Prints a steam locomotive if you type sl instead of ls +brew "sl" +# General purpose fuzzy finder TUI +brew "television" +# Temporary workspace manager for fast experimentation in the terminal +brew "try-rs" +# Generator for LS_COLORS with support for multiple color themes +brew "vivid" +# Internet file retriever +brew "wget" +# Friendly and fast tool for sending HTTP requests +brew "xh" +# Pluggable terminal workspace, with terminal multiplexer as the base feature +brew "zellij" +# Shell extension to navigate your filesystem faster +brew "zoxide" +# Manage git repositories with directory conventions +brew "popomore/tap/projj", trusted: true +# Command-line interface for 1Password +cask "1password-cli" +# Scriptable scratchpad for developers +cask "boop" +# Browser for SQLite databases +cask "db-browser-for-sqlite" +cask "font-departure-mono" +cask "font-geist" +cask "font-geist-mono" +cask "font-iosevka" +cask "font-iosevka-aile" +# GitHub command-line tool +brew "gh" +# Terminal multiplexer +brew "tmux" +# Securely transfers data between computers +brew "magic-wormhole" +# Simple terminal UI for git commands +brew "lazygit" +# Disk Usage/Free Utility - a better 'df' alternative +brew "duf" diff --git a/Brewfile.work b/Brewfile.work index e69de29..77d4ca8 100644 --- a/Brewfile.work +++ b/Brewfile.work @@ -0,0 +1,16 @@ +tap "mike-engel/jwt-cli" +tap "metalbear-co/mirrord" +# Tool that can switch between kubectl contexts easily and create aliases +brew "kubectx" +# Kubernetes command-line interface +brew "kubernetes-cli" +# Template-free customization of Kubernetes YAML manifests +brew "kustomize" +# Connect your local process and your cloud environment +brew "metalbear-co/mirrord/mirrord", trusted: true +# Super fast CLI tool to decode and encode JWTs built in Rust +brew "mike-engel/jwt-cli/jwt-cli", trusted: true +# Kubernetes CLI To Manage Your Clusters In Style! +brew "k9s" +# Vulnerability scanner for container images and filesystems +brew "grype" diff --git a/private_dot_config/jj/config.toml.tmpl b/private_dot_config/jj/config.toml.tmpl index f3f21f7..a321430 100644 --- a/private_dot_config/jj/config.toml.tmpl +++ b/private_dot_config/jj/config.toml.tmpl @@ -28,8 +28,6 @@ edit-args = ["tool", "edit", "$left", "$right"] merge-args = ["tool", "merge", "$left", "$base", "$right", "$output", "$path", "$marker_length"] merge-tool-edits-conflict-markers = true -[templates] -draft_commit_description = "builtin_draft_commit_description_with_diff" [signing] behavior = "drop" -- 2.51.2 From 20ff446dafc8fbf8e2a1b6d564c710de15c2d814 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 12:18:37 -0700 Subject: [PATCH 10/16] Update Brewfiles --- Brewfile | 9 +++++++++ Brewfile.ignore | 26 ++++++++++++++++++++++++++ Brewfile.work | 2 ++ 3 files changed, 37 insertions(+) create mode 100644 Brewfile.ignore diff --git a/Brewfile b/Brewfile index fe686da..13910d3 100644 --- a/Brewfile +++ b/Brewfile @@ -1,3 +1,4 @@ +tap "hewigovens/tap", "https://github.com/hewigovens/tap.git" tap "popomore/tap" # Record and share terminal sessions brew "asciinema" @@ -110,3 +111,11 @@ brew "magic-wormhole" brew "lazygit" # Disk Usage/Free Utility - a better 'df' alternative brew "duf" +# Native GUI for Jujutsu version control +cask "hewigovens/tap/jayjay" +# GIF image/animation creator/editor +brew "gifsicle" +# Apjanke's fork of the classic cowsay project +brew "cowsay" +# Implementation of the DNS protocols +brew "bind" diff --git a/Brewfile.ignore b/Brewfile.ignore new file mode 100644 index 0000000..0fcceef --- /dev/null +++ b/Brewfile.ignore @@ -0,0 +1,26 @@ +# Installed on purpose but not tracked. brew-drift skips these. +cask "orbstack" +cask "obsidian" +cask "iina" +cask "kitty" +cask "wezterm" +cask "yaak" +cask "sequel-ace" +cask "hiddenbar" +cask "jordanbaird-ice" +cask "zkondor/dist/znotch", trusted: true +cask "linearmouse" +cask "osquery" +cask "homebrew-app" +cask "powershell" +brew "hashicorp/tap/terraform", trusted: true +brew "saml2aws" +brew "telnet" +brew "cmctl" +brew "syft" +brew "curlie" +brew "redis" +brew "ldns" +brew "lsd" +brew "minikube" +tap "hashicorp/tap" diff --git a/Brewfile.work b/Brewfile.work index 77d4ca8..5ddc356 100644 --- a/Brewfile.work +++ b/Brewfile.work @@ -14,3 +14,5 @@ brew "mike-engel/jwt-cli/jwt-cli", trusted: true brew "k9s" # Vulnerability scanner for container images and filesystems brew "grype" +# Official Amazon AWS command-line interface +brew "awscli" -- 2.51.2 From 079a4adcc076c3e803fc0a41074fba29d3b1c880 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 12:22:39 -0700 Subject: [PATCH 11/16] Sort remaining Homebrew packages and add Brewfile.ignore - Track go-task, node, powershell, ngrok, bun, and the 1Password tap - Brewfile.ignore lists packages kept installed without tracking; brew-drift skips them and offers an ignore action Co-Authored-By: Claude Opus 5.5 --- .chezmoiignore | 1 + Brewfile | 12 ++++++++++++ Brewfile.ignore | 2 +- private_dot_local/bin/executable_brew-drift | 21 +++++++++++++-------- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/.chezmoiignore b/.chezmoiignore index 221042f..13255e9 100644 --- a/.chezmoiignore +++ b/.chezmoiignore @@ -4,6 +4,7 @@ LICENSE README.md Brewfile Brewfile.work +Brewfile.ignore {{ if .is_work }} .config/halloy {{ else }} diff --git a/Brewfile b/Brewfile index 13910d3..05e2343 100644 --- a/Brewfile +++ b/Brewfile @@ -1,3 +1,5 @@ +tap "1password/tap", trusted: true +tap "oven-sh/bun" tap "hewigovens/tap", "https://github.com/hewigovens/tap.git" tap "popomore/tap" # Record and share terminal sessions @@ -119,3 +121,13 @@ brew "gifsicle" brew "cowsay" # Implementation of the DNS protocols brew "bind" +# Task is a task runner/build tool that aims to be simpler and easier to use +brew "go-task" +# Open-source, cross-platform JavaScript runtime environment +brew "node" +# Command-line shell and scripting language +brew "powershell" +# Reverse proxy, secure introspectable tunnels to localhost +cask "ngrok" +# Incredibly fast JavaScript runtime, bundler, transpiler and package manager - all in one. +brew "oven-sh/bun/bun", trusted: true diff --git a/Brewfile.ignore b/Brewfile.ignore index 0fcceef..ca1d1eb 100644 --- a/Brewfile.ignore +++ b/Brewfile.ignore @@ -21,6 +21,6 @@ brew "syft" brew "curlie" brew "redis" brew "ldns" -brew "lsd" brew "minikube" tap "hashicorp/tap" +tap "zkondor/dist", trusted: true diff --git a/private_dot_local/bin/executable_brew-drift b/private_dot_local/bin/executable_brew-drift index 4c3c081..e72e62f 100644 --- a/private_dot_local/bin/executable_brew-drift +++ b/private_dot_local/bin/executable_brew-drift @@ -1,15 +1,17 @@ #!/usr/bin/env fish # Sort Homebrew drift into the chezmoi Brewfiles. # -# Installed but untracked: track as shared or work, uninstall, or skip. +# Installed but untracked: track as shared or work, ignore, uninstall, or skip. +# Brewfile.ignore lists packages kept installed on purpose without tracking. # Tracked but not installed: install, remove from the Brewfile, or skip. set -x HOMEBREW_NO_AUTO_UPDATE 1 set src (chezmoi source-path); or exit 1 set shared $src/Brewfile set work $src/Brewfile.work +set ignore $src/Brewfile.ignore set is_work (chezmoi execute-template '{{ .is_work }}') -touch $shared $work +touch $shared $work $ignore # Print "typenameentry linedescription" for each tap/brew/cask. function entries @@ -115,11 +117,12 @@ set tracked (cat $files | entries) set installed_keys (for i in $installed; key $i; end) set tracked_keys (for t in $tracked; key $t; end) -set untracked (for i in $installed; contains -- (key $i) $tracked_keys; or echo $i; end) +set ignored_keys (cat $ignore | entries | while read -l e; key $e; end) +set untracked (for i in $installed; contains -- (key $i) $tracked_keys $ignored_keys; or echo $i; end) set missing (for t in $tracked; contains -- (key $t) $installed_keys; or echo $t; end) -set actions shared skip uninstall -test "$is_work" = true; and set actions shared work skip uninstall +set actions shared ignore skip uninstall +test "$is_work" = true; and set actions shared work ignore skip uninstall while test (count $untracked) -gt 0 set picked (printf '%s\n' $untracked | pick (count $untracked)" installed but untracked. Tab selects, Enter confirms, Esc moves on."); or break @@ -135,6 +138,8 @@ while test (count $untracked) -gt 0 track $p $shared case work track $p $work + case ignore + echo $f[3] >>$ignore case uninstall switch $f[1] case tap @@ -165,6 +170,6 @@ while test (count $missing) -gt 0 end echo -git -C $src --no-pager diff --stat -- Brewfile Brewfile.work -git -C $src status --short -- Brewfile Brewfile.work -echo "Commit with: chezmoi git -- add Brewfile Brewfile.work; and chezmoi git -- commit -m 'Update Brewfiles'" +git -C $src --no-pager diff --stat -- Brewfile Brewfile.work Brewfile.ignore +git -C $src status --short -- Brewfile Brewfile.work Brewfile.ignore +echo "Commit with: chezmoi git -- add Brewfile Brewfile.work Brewfile.ignore; and chezmoi git -- commit -m 'Update Brewfiles'" -- 2.51.2 From a53f7579471870896602d208dbacf168068ac57c Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 12:40:13 -0700 Subject: [PATCH 12/16] Push personal repos as plttn over SSH; keep mise go_set_goroot - Work SSH config (encrypted, now a template) uses the personal key in the chezmoi source dir and plttn/1Password/agilebits repo folders; EMU stays the default for go get - Add the personal public key for that rule, work machines only - mise: go_set_goroot = false Co-Authored-By: Claude Opus 5.5 --- .chezmoiignore | 1 + private_dot_config/mise/config.toml.tmpl | 1 + .../config.d/encrypted_private_work.age | 17 ------------ .../config.d/encrypted_private_work.tmpl.age | 26 +++++++++++++++++++ .../private_id_ed25519-personal.pub.tmpl | 1 + 5 files changed, 29 insertions(+), 17 deletions(-) delete mode 100644 private_dot_ssh/config.d/encrypted_private_work.age create mode 100644 private_dot_ssh/config.d/encrypted_private_work.tmpl.age create mode 100644 private_dot_ssh/private_id_ed25519-personal.pub.tmpl diff --git a/.chezmoiignore b/.chezmoiignore index 13255e9..29c93e8 100644 --- a/.chezmoiignore +++ b/.chezmoiignore @@ -11,6 +11,7 @@ Brewfile.ignore .config/git/github-work.conf .config/git/github-emu.conf .ssh/id_ed25519-emu.pub +.ssh/id_ed25519-personal.pub .ssh/config.d .config/git/work.conf .config/jj/conf.d diff --git a/private_dot_config/mise/config.toml.tmpl b/private_dot_config/mise/config.toml.tmpl index ed4c371..cf8ea4a 100644 --- a/private_dot_config/mise/config.toml.tmpl +++ b/private_dot_config/mise/config.toml.tmpl @@ -18,3 +18,4 @@ zig = "0.15.2" experimental = true idiomatic_version_file_enable_tools = ["rust"] auto_update = true +go_set_goroot = false diff --git a/private_dot_ssh/config.d/encrypted_private_work.age b/private_dot_ssh/config.d/encrypted_private_work.age deleted file mode 100644 index 178e703..0000000 --- a/private_dot_ssh/config.d/encrypted_private_work.age +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxd1hiMHZ3cS9ESFVqSkpi -ODYvZUU4T2pjemV1endLbUxNMnBKWDR0eWo4ClRyY3FBL2ZIb3hBc0phUFlEZ016 -T29GUjQycHlvSHROOTVtR0VVVlFITjAKLS0tIEpmOWk2NGlZNEVlZjZhd2RzUUZz -cU5ITSs5K2pNaUkwMlB4VE1jMGcvT1EKqiEwfHaQ+QZed65ipuE/DLquBA50C2dr -cgQMoKi/Gujhgi3nPlE4SSG0xDhWvdEFnOWle3bzSaJMw8iYiUbPZPSxWpiwXcMY -2hJX0w0IrCPImyJZPDZwD5ykayhypQA4meP0TasMhrc1ehK0/jiQGIPJCORTcwjo -ZSgHkHHysOS9QcHU9Zdy4tX2PxPhTcmeOEJ5cMjH6qIK/IeeyE00m/iBD0IOXAXJ -wY7/q83tlu1ZrKrX+ofe1xvqTPKuDQZbpephF71SmUOb6TKObYdin5SLBWGoMpED -F6T8cN8rFUpX9BkYHST29XANe7fBp18b+D6UPzUDIiQjxKu5j78E+SnaHpod5SnO -m3yokkgwxU4tMlnVuJY0EMwd8BJgkVPOPBI0YFSi4yX3oPE+i0bQLmAjrdA19a6R -JkUfvN/G+Knq/hZi/saY679igvO5IZe725mm4y8aBJ1GPvSdTrEmFGvJKxbXdXR6 -EAWAcOf0U3CUq9YCGJIzAM/g0fcMSbGMaeEEx5AVtSgfcYJ/6RzYM8spwEXzKW+F -xUPBF9P4dzEIwacUsqQ8m+b+Eg20+DvLQE/A7LdLkk8Vj9j7vNt1JMp4Xvai1/fG -I9pL5QwOdq3qnFk8NOpuBJTKCWVxcRDNi2+lc3Atvu9/tQfYWtHoG9xppz/ZiWF3 -wkfckr+Ww6PZCP8uFykhbuleRLTM3UxpQpo= ------END AGE ENCRYPTED FILE----- diff --git a/private_dot_ssh/config.d/encrypted_private_work.tmpl.age b/private_dot_ssh/config.d/encrypted_private_work.tmpl.age new file mode 100644 index 0000000..44183c4 --- /dev/null +++ b/private_dot_ssh/config.d/encrypted_private_work.tmpl.age @@ -0,0 +1,26 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6azRlenVpOHF0VjR0T1Uw +enVRQ2NCRS9aNG05TFhjaEpJaE9GdTRQQkZvCmF3ZFQwS21SSjRkNHBvRlFScUk3 +L2ZpOEdSR3ZZMzc1dEJTM2NKWFJFaE0KLS0tIEZQRS9LZnY3MlZVV2ZsZ2ZBSGtR +QjhiQ05IdjJEK3ZtZFFuS1MwbWdWY0kKW5g2yRnDW1YfwemqefPVZBUA1e2qClqX +O9LKMtFmXsQg67rZrvgX6qGtgOF4EUQ2rvtlSXsBuDYhJwwjOrNmA8+EFkik21y0 +o7fU68VmjPBvdXVlzkPdcljM0XY+/bbEX3oDZmv/AID3LfIgsICiF3g4AiTo+IV3 +BxFA7voc7m+IE7VN/QmlDf8QxVpqSNT6WT/0ytC7etuSOYE61b0M4Kg4vgTFrkvg +vwJC/jQZviGL3bFBksZlXs6VjtY833KWSigB080HsM7cEuTPt97xEl1Xv5EtKqta +ZZhKaU2kQhFis0F/oOfiYKi+jz1ld62n7qmDWevgmdm5gcoXpD/HFi74VJHA2Kd4 +Vr9nCikl8sxGrJCtrqfCtnqLtoUTU6GikNJmQGz6w8OVBZQHtAIpcrWubL5ABTyz +xKlF/Pjy/q+D0K1qgiAhOBouJgxggHzZS0Cq4INIlA2Uem/UPPLUeky/fEuGboa5 +0qVe6gynI95GSXpIbZSz+rLQq31ocs27eSSPF1/mliRBZW6d68ZcVspn/k8PLviX +7IPQRY7FhS3hkxFLS04t/3DF/qHntUb5pXzd6VWNLsKmna7A1bI7MTEXDOVTc1c5 +tm2c3Cv3VT3b7n4wwk45sgIDjCJbAdSu3Ry3noNozQnrE1IeWY6pBf3pPXH+xHj2 +cHIAeDQxgW3eGDajF/fM2QpcTmZB05/OqS6Nj1ZtEkDfpaHgUSiEmtwVqIXC1IWY +pFqo1kx685ndzlivMWZIHKhaBAKlMqLpXAJjlBNseGv7OcjrCGufqWQbGK37wDHY +Z5JGhqSVvVBOK+E34Fgwv5JKqsAhJxqGy9sBB/oJ1ivk902U7KIp6HVF98q1pPLA +sEBIRuxJsVHWIIaOESLQWsBlkfkPbBmt1qrW0qhPEL078jnKVjKaWrLJDXAy8AZm +qD32jK9bYtbCZSjiaI5d6o1ZDDeikPNQgIfJcfyhxe0qqUNj2gK/K2DMlqEDKkvk +/aPm1rmcVniLZAeA/0LQdy29WGioLQZlXB8Tz8khQGPa/jzRP2d/uJipsPtewlVt +DlTugECivZM+W0BvUxpsCjVLhzkAdUOs0OkoBE0Cl53kipj07R1pAPACy3h8+M2f +cKuwalk9gWMZhuqTMaOSF3kSjM2xtK7KasBQZ1+MfoqQ94ZYM3KXVS3sgOF8yYkd +D1v1umP06h7Z7qKJ42y366C3dOARKKVdn9mQ9zIkDxgR7boPkuythIcDepQ+B2tU +1YdQM8n2jXt7Q4UqnCdy++W/caFxQd6F2Y6MwmmkSReRl/1y0qs= +-----END AGE ENCRYPTED FILE----- diff --git a/private_dot_ssh/private_id_ed25519-personal.pub.tmpl b/private_dot_ssh/private_id_ed25519-personal.pub.tmpl new file mode 100644 index 0000000..34050be --- /dev/null +++ b/private_dot_ssh/private_id_ed25519-personal.pub.tmpl @@ -0,0 +1 @@ +{{ .keys.personal }} -- 2.51.2 From f597402201e8d459c8a44735c5373f0ec6e9c195 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 12:44:09 -0700 Subject: [PATCH 13/16] Document the setup in README.md and add CLAUDE.md for agents Co-Authored-By: Claude Opus 5.5 --- .chezmoiignore | 1 + CLAUDE.md | 75 +++++++++++++++++++++++++++++++++++++++++++ README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/.chezmoiignore b/.chezmoiignore index 29c93e8..baad8a4 100644 --- a/.chezmoiignore +++ b/.chezmoiignore @@ -2,6 +2,7 @@ install.sh LICENSE README.md +CLAUDE.md Brewfile Brewfile.work Brewfile.ignore diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6dcd21d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,75 @@ +# Working in this repo + +This is a chezmoi source directory for public dotfiles. Read README.md for the +layout. These rules cover what isn't obvious from the files. + +## The repo is public + +- Internal names (work hosts, org names, vault and item names, work emails, + internal tool names) only go in encrypted files. That includes comments and + commit messages. +- Before any push, search unencrypted tracked files and the unpushed history: + + ```sh + git ls-files | grep -v '\.age$' | xargs grep -niE '' + git --no-pager log -p --no-ext-diff origin/main..HEAD -- . ':(exclude)*.age' + ``` + +- Public keys are fine to commit. Private keys never are. + +## SSH and GitHub + +- Never add a global `Host github.com` rule, and never change which key the + agent offers by default. The work identity must stay the default: private Go + modules clone from Go's module cache, outside any folder rule, and a + different default breaks them. +- Per-identity behavior lives in folder-based `Match exec` rules in the + encrypted `~/.ssh/config.d/work`. Extend those instead. +- The dotfiles remote uses SSH. Don't switch it to HTTPS: `gh` supplies the + token there, and its active account is the work one. +- Check an identity with `ssh -T git@github.com` from the folder in question. + +## chezmoi gotchas + +- `git.autoCommit = true`: `chezmoi add`, `forget` and similar commands commit + everything pending in the source directory. Commit or ask about pending work + first. +- chezmoi prompts before overwriting a file that changed since its last write. + The prompt needs a TTY and fails here. Diff the file, keep anything the user + added (copy it into the source first), and only then use `--force`. +- Files in the repo root (README.md, CLAUDE.md, the Brewfiles) must stay in + `.chezmoiignore`, or chezmoi deploys them to `~`. +- Work-only targets belong in the `else` branch of `.chezmoiignore`. +- `chezmoi edit` opens an interactive editor and won't work here. For + encrypted files: `chezmoi decrypt < file.age > tmp`, edit, then + `chezmoi encrypt < tmp > file.age`. Keep the `.tmpl.age` suffix for + encrypted templates. +- Templates use `missingkey=error`; a new data key needs a value on every + machine (`""` or `false` when it doesn't apply). + +## Testing changes + +- Render templates for both machine types with scratch configs instead of the + real one: write a config with `is_work = true` and one with `false`, then + `chezmoi --config execute-template < file.tmpl` or + `chezmoi --config cat ~/target`. +- `chezmoi --config managed` shows which targets a machine type gets. +- `brew-drift` needs fzf and a TTY. Test it with `fish --no-config` (so the + user's fish config doesn't reorder `PATH`) and a stand-in `fzf` script that + prints scripted selections. + +## 1Password + +- Each `op` call costs the user a biometric approval. Never run `op` to + explore. Templates that call `onepasswordRead` cost one approval per + `chezmoi diff`/`status`/`apply` on machines that render them. +- The age key fetch script runs `op read` only when the key file is missing. + +## Homebrew + +- `brew bundle dump` prints the installed set in Brewfile form, with options + such as `trusted: true` and custom tap URLs. Copy entries from it rather + than writing them by hand or with `brew bundle add`, which drops options. +- `brew bundle remove --formula` fails for formulae that no longer exist, and + without `--formula` it leaves the description comment behind. + `brew-drift` edits the text directly for that reason. diff --git a/README.md b/README.md index 9b44516..df9e6d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,90 @@ # dotfiles -Template dotfiles repository, managed with [chezmoi](https://chezmoi.io/). +Personal dotfiles, managed with [chezmoi](https://chezmoi.io/). One repo serves +both personal and work Macs; a per-machine flag decides what each one gets. + +## New machine + +```sh +chezmoi init plttn/dotfiles # prompts below +chezmoi diff # review before writing anything +chezmoi apply +fisher update # fish plugins from fish_plugins +mise install # tools from mise/config.toml +``` + +`chezmoi init` asks for: + +- **Name** and **Email**: the personal identity, used everywhere by default. +- **Is this a work machine**: sets `is_work`. +- **Work email**: only asked on work machines. + +The first `chezmoi apply` fetches the age key from 1Password (one approval), so +the 1Password CLI needs access to the personal account. After that the key +lives at `~/.config/chezmoi/key.txt` and decryption needs no prompts. + +Re-run `chezmoi init` whenever `.chezmoi.toml.tmpl` changes; chezmoi warns when +it does. `chezmoi init --data=false` asks every question again. + +## Personal and work machines + +Templates branch on `is_work`: + +- **Shared files** go everywhere. Lines that differ use `{{ if .is_work }}`. +- **Work-only files** are age-encrypted (`encrypted_*.age`) and listed in the + `else` branch of `.chezmoiignore`, so personal machines never get them. +- **Personal-only files** go in the `if .is_work` branch of `.chezmoiignore`. + +Tools that read a whole directory make this easy: fish loads +`~/.config/fish/conf.d/*`, jj loads `~/.config/jj/conf.d/*.toml`, and the SSH +config includes `~/.ssh/config.d/*`. Work settings go in an encrypted file in +those directories, so the public config stays generic. Git does the same with +an `[include]` of an encrypted `work.conf`. + +### Identities + +Public keys live in `.chezmoidata.toml`. Git and jj use the personal identity +by default on personal machines and the work identity by default on work +machines, with per-org overrides in the encrypted work files. + +## Encryption + +This repo is public. Anything that names internal hosts, orgs, vaults, people +or tools goes in an encrypted file: + +```sh +chezmoi add --encrypt ~/path/to/file +chezmoi edit ~/path/to/file # decrypts, opens the editor, re-encrypts +``` + +Encrypted files can also be templates (`encrypted_name.tmpl.age`). + +## Homebrew + +Three files in the repo root track packages. chezmoi doesn't deploy them. + +| File | Installed on | +|---|---| +| `Brewfile` | every machine | +| `Brewfile.work` | work machines | +| `Brewfile.ignore` | nowhere: installed on purpose, not tracked | + +- **Sorting drift:** run `brew-drift` now and then. It lists what's installed + but untracked (track as shared or work, ignore, uninstall, skip) and what's + tracked but missing (install, remove, skip). Packages from third-party taps + bring their tap along. +- **Installing:** `chezmoi apply` runs `brew bundle install --no-upgrade` + whenever a Brewfile changes. It installs what's missing and never upgrades. +- **Committing:** `brew-drift` prints the commit command when it finishes. + +## Notes + +- `git.autoCommit` is on, so `chezmoi add` and `chezmoi forget` commit + everything pending in the source directory. Commit other edits first. +- chezmoi doesn't delete files that leave the repo. Remove them by hand, or + list them in `.chezmoiremove`. +- `~/.claude/CLAUDE.md` holds the shared Claude Code instructions. On work + machines it imports an encrypted `~/.claude/work.md`. ## License -- 2.51.2 From a9b34b0d2425b70353c8b9353c32b5ac43dec6d9 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 13:40:09 -0700 Subject: [PATCH 14/16] Update .config/jj/config.toml --- private_dot_config/jj/config.toml.tmpl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/private_dot_config/jj/config.toml.tmpl b/private_dot_config/jj/config.toml.tmpl index a321430..6596c9f 100644 --- a/private_dot_config/jj/config.toml.tmpl +++ b/private_dot_config/jj/config.toml.tmpl @@ -48,6 +48,9 @@ bookmark-advance-to = 'closest_pushable(@)' 'closest_pushable(to)' = ''' heads(::to & mutable() & ~description(exact:"") & (~empty() | merges())) ''' +# unless you're really sure, you don't wanna force push +'immutable_heads()' = 'builtin_immutable_heads() | remote_bookmarks()' + [aliases] pr = ["util", "exec", "--", "jj-gh", "pr"] -- 2.51.2 From 75ee7f52338e177cf9aa449d8a5ce8cebf130a07 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 13:58:19 -0700 Subject: [PATCH 15/16] Update .config/jj/config.toml --- private_dot_config/jj/config.toml.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/private_dot_config/jj/config.toml.tmpl b/private_dot_config/jj/config.toml.tmpl index 6596c9f..7e34aa2 100644 --- a/private_dot_config/jj/config.toml.tmpl +++ b/private_dot_config/jj/config.toml.tmpl @@ -49,7 +49,7 @@ bookmark-advance-to = 'closest_pushable(@)' heads(::to & mutable() & ~description(exact:"") & (~empty() | merges())) ''' # unless you're really sure, you don't wanna force push -'immutable_heads()' = 'builtin_immutable_heads() | remote_bookmarks()' +'immutable_heads()' = 'builtin_immutable_heads() | (trunk().. & ~mine())' [aliases] -- 2.51.2 From 147f6260fd372f5909a37c1ffef1d72238e94595 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 8 Oct 2026 14:02:01 -0700 Subject: [PATCH 16/16] Update .config/jj/config.toml --- private_dot_config/jj/config.toml.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/private_dot_config/jj/config.toml.tmpl b/private_dot_config/jj/config.toml.tmpl index 7e34aa2..958a9fc 100644 --- a/private_dot_config/jj/config.toml.tmpl +++ b/private_dot_config/jj/config.toml.tmpl @@ -49,8 +49,7 @@ bookmark-advance-to = 'closest_pushable(@)' heads(::to & mutable() & ~description(exact:"") & (~empty() | merges())) ''' # unless you're really sure, you don't wanna force push -'immutable_heads()' = 'builtin_immutable_heads() | (trunk().. & ~mine())' - +'immutable_heads()' = 'builtin_immutable_heads() | (trunk().. & ~mine()) | (remote_bookmarks() & @)' [aliases] pr = ["util", "exec", "--", "jj-gh", "pr"]