From 5d4f89ed2f004cf6864de65ea5270de08aab81a2 Mon Sep 17 00:00:00 2001 From: Christian Emmer <10749361+emmercm@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:29:44 -0700 Subject: [PATCH 1/2] 4/20/2026 --- .claude/CLAUDE.md | 9 +++++++ .everythingrc | 22 +++++++++++++++++- dotpack/.20_java.sh | 5 ++-- dotpack/.20_nodejs.sh | 4 ++-- dotpack/.20_python.sh | 1 + dotpack/.20_ruby.sh | 4 ++-- dotpack/.30_aws.sh | 12 +++++----- dotpack/.30_claude.sh | 49 ++++++++++++++++++++++++++++++++------- dotpack/.30_docker.sh | 8 +++---- dotpack/.30_git.sh | 2 +- dotpack/.30_kubernetes.sh | 12 +++++----- dotpack/.30_mysql.sh | 1 + dotpack/.30_temporal.sh | 16 ++++++------- dotpack/.30_terraform.sh | 8 +++---- dotpack/.30_vitess.sh | 12 +++++----- install.sh | 6 ++--- packages.sh | 27 ++++++++++++++------- settings.sh | 7 +++--- 18 files changed, 140 insertions(+), 65 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 1a6c8cb..61447a2 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -21,3 +21,12 @@ Always execute tasks in this order: ## Git - Do not ever commit to the default, `main`, or `master` Git branches. Always ask if a new branch should be created instead. +- Always prefix git branch names with "emmercm/". + +## Superpowers + +- Do not ever commit specs, plans, or any other documents produced by the "superpowers" plugin. + +## Writing style + +- Use American English spelling in comments and documentation diff --git a/.everythingrc b/.everythingrc index ed9e2e0..35c32a1 100755 --- a/.everythingrc +++ b/.everythingrc @@ -1,7 +1,8 @@ # Bash: .bash_profile (macOS default) -> .profile (Ubuntu default) -> .bashrc -> .everythingrc # zsh: .zshrc (always executed) -> .everythingrc -SHELL=$(which "$(ps -o comm= $$ | sed 's/^-*//')") +: "${SHELL:=$(which "$(ps -o comm= $$ | sed 's/^-*//')" 2>/dev/null)}" +export SHELL ##### Bash ##### @@ -27,6 +28,25 @@ __bashrc_misc() { return 1 } + # @param {string} $1 Name of the throttle key + # @param {number} $2 Cooldown duration in seconds + throttle() { + local state_dir="${XDG_STATE_HOME:-${HOME}/.local/state}/throttle" + local state_file="${state_dir}/$1" + local now + now=$(date +%s) + local last_run=0 + if [[ -f "${state_file}" ]]; then + last_run=$(cat "${state_file}" 2>/dev/null || echo 0) + fi + if (( now - last_run > $2 )); then + mkdir -p "${state_dir}" + echo "${now}" > "${state_file}" + return 0 + fi + return 1 + } + # Don't be a savage if command -v nano &> /dev/null; then EDITOR=$(pinpoint nano) diff --git a/dotpack/.20_java.sh b/dotpack/.20_java.sh index aae991e..4a0ef18 100644 --- a/dotpack/.20_java.sh +++ b/dotpack/.20_java.sh @@ -7,6 +7,7 @@ __java_setup() { fi if [[ "${JAVA_HOME:-}" == "" ]]; then while read -r DIR; do + [[ -z "${DIR}" ]] && continue export JAVA_HOME="${DIR}" done <<< "$(find /Library/Java/JavaVirtualMachines/*/Contents -maxdepth 1 -type d -name Home 2> /dev/null | sort --version-sort)" fi @@ -22,8 +23,8 @@ __java_lazy_install() { if ! command -v flyway &> /dev/null && command -v brew &> /dev/null; then flyway() { brew install flyway - unset -f "$0" - $0 "$@" + unset -f flyway + flyway "$@" } fi } diff --git a/dotpack/.20_nodejs.sh b/dotpack/.20_nodejs.sh index 23f1e3a..64ca317 100644 --- a/dotpack/.20_nodejs.sh +++ b/dotpack/.20_nodejs.sh @@ -35,9 +35,9 @@ __nodejs_bun() { # Lazy-load shell completions if [[ -d "$HOME/.bun/_bun" ]]; then bun() { - unset -f "$0" + unset -f bun source "$HOME/.bun/_bun" - $0 "$@" + bun "$@" } fi } diff --git a/dotpack/.20_python.sh b/dotpack/.20_python.sh index 40b72ea..1d1cc57 100644 --- a/dotpack/.20_python.sh +++ b/dotpack/.20_python.sh @@ -2,6 +2,7 @@ __python_setup() { while read -r DIR; do + [[ -z "${DIR}" ]] && continue export PATH="${DIR}:${PATH}" done <<< "$(find ~/Library/Python/* -maxdepth 1 -type d -name bin 2> /dev/null)" } diff --git a/dotpack/.20_ruby.sh b/dotpack/.20_ruby.sh index 038979c..7b4aafd 100644 --- a/dotpack/.20_ruby.sh +++ b/dotpack/.20_ruby.sh @@ -4,10 +4,10 @@ __ruby_chruby() { # Lazy load chruby if [[ -d "${HOMEBREW_PREFIX}/opt/chruby/share/chruby" ]]; then chruby() { - unset -f "$0" + unset -f chruby source "${HOMEBREW_PREFIX}/opt/chruby/share/chruby/chruby.sh" source "${HOMEBREW_PREFIX}/opt/chruby/share/chruby/auto.sh" - $0 "$@" + chruby "$@" } fi diff --git a/dotpack/.30_aws.sh b/dotpack/.30_aws.sh index 60ccda5..ca63034 100644 --- a/dotpack/.30_aws.sh +++ b/dotpack/.30_aws.sh @@ -3,10 +3,10 @@ __aws_completions() { if command -v aws &> /dev/null && command -v aws_completer &> /dev/null; then aws() { - unset -f "$0" + unset -f aws # https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-completion.html#cli-command-completion-linux complete -C '/usr/local/bin/aws_completer' aws - $0 "$@" + aws "$@" } fi } @@ -17,16 +17,16 @@ __aws_lazy_install() { if ! command -v aws &> /dev/null && command -v brew &> /dev/null; then aws() { brew install awscli - unset -f "$0" - $0 "$@" + unset -f aws + aws "$@" } fi if ! command -v awslocal &> /dev/null && command -v brew &> /dev/null; then awslocal() { pip3 install awscli-local - unset -f "$0" - $0 "$@" + unset -f awslocal + awslocal "$@" } fi } diff --git a/dotpack/.30_claude.sh b/dotpack/.30_claude.sh index 641ae9b..0283e1d 100644 --- a/dotpack/.30_claude.sh +++ b/dotpack/.30_claude.sh @@ -1,21 +1,34 @@ ##### https://code.claude.com/docs/en/overview +export PATH="$HOME/.local/bin:$PATH" + export RTK_TELEMETRY_DISABLED=1 +__claude_update() { + # Auto-update Claude once a day + if [[ "${1:-}" != "update" ]] && throttle "CLAUDE_AUTO_UPDATE_SECS" 86400; then + command claude update + fi + + # Auto-update Claude marketplaces once a week + if throttle "CLAUDE_AUTO_UPDATE_PLUGINS_SECS" 604800; then + command claude plugin marketplace list --json | jq -r '.[].name' | xargs -I {} command claude plugin marketplace update {} + fi +} + __claude_auto_update() { if command -v claude &> /dev/null; then claude() { - if [[ "${1:-}" != "update" ]]; then - command claude update + # Commands to skip updating before + if [[ " $@ " = *" -h "* || " $@ " == *" --help "* || " $@ " == *" -p "* || " $@ " == *" --print "* ]]; then + command claude "$@" + return $? fi - #if [[ " $@ " != *" --resume "* && " $@ " != *" --session-id "* ]]; then - # command claude "$@" --continue - #else - # command claude "$@" - #fi + __claude_update command claude "$@" + return $? } fi } @@ -26,9 +39,27 @@ __claude_lazy_install() { if ! command -v claude &> /dev/null; then claude() { curl -fsSL https://claude.ai/install.sh | bash - unset -f "$0" - $0 "$@" + unset -f claude + claude "$@" } fi } __claude_lazy_install + + +__agent_deck_auto_update() { + if command -v agent-deck &> /dev/null; then + agent-deck() { + __claude_update + + # Auto-update Claude once a day + if [[ "${1:-}" != "update" ]] && throttle "AGENT_DECK_AUTO_UPDATE_SECS" 86400; then + yes | command agent-deck update + fi + + command agent-deck "$@" + return $? + } + fi +} +__agent_deck_auto_update diff --git a/dotpack/.30_docker.sh b/dotpack/.30_docker.sh index f5e9d4d..d8536b9 100644 --- a/dotpack/.30_docker.sh +++ b/dotpack/.30_docker.sh @@ -8,16 +8,16 @@ __docker_lazy_install() { if ! command -v dive &> /dev/null && command -v brew &> /dev/null; then dive() { brew install dive - unset -f "$0" - $0 "$@" + unset -f dive + dive "$@" } fi if ! command -v skopeo &> /dev/null && command -v brew &> /dev/null; then skopeo() { brew install skopeo - unset -f "$0" - $0 "$@" + unset -f skopeo + skopeo "$@" } fi } diff --git a/dotpack/.30_git.sh b/dotpack/.30_git.sh index c363669..0c43324 100644 --- a/dotpack/.30_git.sh +++ b/dotpack/.30_git.sh @@ -56,7 +56,7 @@ __git_funcs() { local origin_old origin_old="$(git remote get-url origin)" local origin_new - origin_new="${origin_old/https:\/\/github.co\//git@github.com:}" + origin_new="${origin_old/https:\/\/github.com\//git@github.com:}" if [[ "${origin_new}" != "${origin_old}" && "${origin_new}" != "" ]]; then echo "Changing $(pwd) remote origin to: ${origin_new}" git remote set-url origin "${origin_new}" diff --git a/dotpack/.30_kubernetes.sh b/dotpack/.30_kubernetes.sh index 17b19ca..471e979 100644 --- a/dotpack/.30_kubernetes.sh +++ b/dotpack/.30_kubernetes.sh @@ -4,8 +4,8 @@ __kube_lazy_install() { if ! command -v k9s &> /dev/null && command -v brew &> /dev/null; then k9s() { brew install k9s - unset -f "$0" - $0 "$@" + unset -f k9s + k9s "$@" } fi @@ -13,16 +13,16 @@ __kube_lazy_install() { # Note: Docker Desktop provides kubectl, this shouldn't be necessary kubectl() { brew install kubectl - unset -f "$0" - $0 "$@" + unset -f kubectl + kubectl "$@" } fi if ! command -v stern &> /dev/null && command -v brew &> /dev/null; then stern() { brew install stern - unset -f "$0" - $0 "$@" + unset -f stern + stern "$@" } fi } diff --git a/dotpack/.30_mysql.sh b/dotpack/.30_mysql.sh index a0aaab0..887cc78 100644 --- a/dotpack/.30_mysql.sh +++ b/dotpack/.30_mysql.sh @@ -2,6 +2,7 @@ __mysql_setup() { # Set path environment variables if command -v brew &> /dev/null; then while read -r DIR; do + [[ -z "${DIR}" ]] && continue if [[ -d "${DIR}/bin" ]]; then export PATH="${DIR}/bin:${PATH}" fi diff --git a/dotpack/.30_temporal.sh b/dotpack/.30_temporal.sh index 6a48729..b424e44 100644 --- a/dotpack/.30_temporal.sh +++ b/dotpack/.30_temporal.sh @@ -15,17 +15,17 @@ __temporal_completions() { if command -v tctl &> /dev/null; then tctl() { - unset -f "$0" + unset -f tctl # shellcheck disable=SC1090 source <(tctl completion "$(basename "${SHELL}")") - $0 "$@" + tctl "$@" } temporal() { - unset -f "$0" + unset -f temporal # shellcheck disable=SC1090 source <(temporal completion "$(basename "${SHELL}")") - $0 "$@" + temporal "$@" } fi } @@ -36,16 +36,16 @@ __temporal_lazy_install() { if ! command -v tctl &> /dev/null && command -v brew &> /dev/null; then tctl() { brew install tctl - unset -f "$0" - $0 "$@" + unset -f tctl + tctl "$@" } fi if ! command -v temporal &> /dev/null && command -v brew &> /dev/null; then temporal() { brew install temporal - unset -f "$0" - $0 "$@" + unset -f temporal + temporal "$@" } fi } diff --git a/dotpack/.30_terraform.sh b/dotpack/.30_terraform.sh index 83b8cd0..252dbbb 100644 --- a/dotpack/.30_terraform.sh +++ b/dotpack/.30_terraform.sh @@ -10,8 +10,8 @@ __terraform_install() { brew tap hashicorp/tap brew install hashicorp/tap/terraform fi - unset -f "$0" - $0 "$@" + unset -f terraform + terraform "$@" } } __terraform_install @@ -29,8 +29,8 @@ __tflint_install() { docker run --rm --volume "$(pwd):/data" --tty "ghcr.io/terraform-linters/tflint" "$@" return $? fi - unset -f "$0" - $0 "$@" + unset -f tflint + tflint "$@" } } __tflint_install diff --git a/dotpack/.30_vitess.sh b/dotpack/.30_vitess.sh index 8032175..2b200a2 100644 --- a/dotpack/.30_vitess.sh +++ b/dotpack/.30_vitess.sh @@ -4,11 +4,11 @@ __vitess_completions() { if command -v pscale &> /dev/null; then pscale() { - unset -f "$0" + unset -f pscale # https://planetscale.com/docs/cli/completion # shellcheck disable=SC1090 source <(pscale completion "$(basename "${SHELL}")") - $0 "$@" + pscale "$@" } fi } @@ -20,15 +20,15 @@ __vitess_lazy_install() { if ! command -v pscale &> /dev/null && command -v brew &> /dev/null; then pscale() { brew install planetscale/tap/pscale - unset -f "$0" - $0 "$@" + unset -f pscale + pscale "$@" } fi if ! command -v mysql &> /dev/null && command -v brew &> /dev/null; then mysql() { brew install mysql-client - unset -f "$0" - $0 "$@" + unset -f mysql + mysql "$@" } fi } diff --git a/install.sh b/install.sh index e1cfad4..ff5d352 100755 --- a/install.sh +++ b/install.sh @@ -21,7 +21,7 @@ function backup() { # @param {string} $1 Wildcard to find dotfiles function link() { # Create symlinks - while read -r file; do + find "$1" -maxdepth 1 -name "$2" ! -name ".editorconfig" ! -name ".git" ! -name ".githooks" ! -name ".github" ! -name ".gitignore" ! -name ".DS_Store" | while read -r file; do local link link="${HOME}/$(basename "${file}")" @@ -37,7 +37,7 @@ function link() { fi # Back up the existing directory/file - local backup + local backup="" if [[ -e "${link}" ]]; then backup="$(backup "${link}")" echo -e "\033[34mMoving:\033[0m ${link} -> ${backup}" @@ -53,7 +53,7 @@ function link() { echo -e "\033[34mRestoring:\033[0m ${backup}/* -> ${link}/" cp -r "${backup}/." "${link}/" fi - done <<< "$(find "$1" -maxdepth 1 -name "$2" ! -name ".editorconfig" ! -name ".git" ! -name ".githooks" ! -name ".github" ! -name ".gitignore" ! -name ".DS_Store")" + done # Delete broken symlinks find "${HOME}" -maxdepth 1 -type l ! -exec test -e {} \; -print | while read -r file; do diff --git a/packages.sh b/packages.sh index 951d470..b8a3a53 100755 --- a/packages.sh +++ b/packages.sh @@ -10,10 +10,22 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then eval "$(/opt/homebrew/bin/brew shellenv)" fi + # Update Command Line Tools (some Homebrew packages require it) + if softwareupdate --list | grep -q "Label: Command Line Tools"; then + if ! sudo -n true &> /dev/null; then + echo -e "\033[1;33mWARN:\033[0m you may be asked for your password to run 'xcode-select --install' or 'softwareupdate --install'" + fi + CLT_LABEL=$(softwareupdate --list | grep "Label: Command Line Tools" | head -n 1 | sed 's/^.*Label: //') + if [[ -n "${CLT_LABEL}" ]]; then + sudo xcode-select --install || sudo softwareupdate --install "${CLT_LABEL}" + fi + fi + brew update brew upgrade # Install Homebrew formulas + command -v agent-deck > /dev/null || brew install asheshgoplani/tap/agent-deck command -v gawk > /dev/null || brew install gawk command -v gdate > /dev/null || brew install coreutils command -v gh > /dev/null || brew install gh @@ -36,14 +48,13 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then command -v wget > /dev/null || brew install wget # Install Homebrew casks (only install if shell is interactive, in case of admin password prompt) - brew tap emmercm/igir - brew tap nahive/spotify-notify if ! sudo -n true &> /dev/null; then - echo -e "\033[1;33mWARN:\033[0m you may be asked for your password to run 'brew install --cask'\n" + echo -e "\033[1;33mWARN:\033[0m you may be asked for your password to run 'brew install --cask'" fi for cask in $( echo "1password" echo "charmstone" + echo "claude" #echo "discord" echo "disk-expert" echo "docker-desktop" @@ -64,7 +75,7 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then #echo "signal" #echo "slack" echo "spotify" - echo "spotify-notify" + echo "nahive/spotify-notify/spotify-notify" #echo "steam" #echo "telegram" echo "visual-studio-code" @@ -74,7 +85,7 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then printf "Checking for cask '${cask}' ... " missing_apps=$(brew info --json=v2 --cask "${cask}" \ | jq --raw-output ".casks[] | select(.token==\"${cask}\") | .artifacts[] | .app // empty | .[]" \ - | xargs -I {} sh -c 'test ! -d "/Applications/{}" && echo "{}"' || true) + | xargs -I {} sh -c 'test ! -d "/Applications/$1" && echo "$1"' _ {} || true) if [[ -n "${missing_apps}" ]]; then echo -e "\033[33mmissing, installing ...\033[0m" brew install --cask "${cask}" @@ -85,7 +96,7 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then # Uninstall old Homebrew casks if ! sudo -n true &> /dev/null; then - echo -e "\033[1;33mWARN:\033[0m you may be asked for your password to run 'brew uninstall --cask'\n" + echo -e "\033[1;33mWARN:\033[0m you may be asked for your password to run 'brew uninstall --cask'" fi for cask in $( echo "messenger" @@ -93,7 +104,7 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then printf "Checking for cask '${cask}' ... " existing_apps=$(brew info --json=v2 --cask "${cask}" \ | jq --raw-output ".casks[] | select(.token==\"${cask}\") | .artifacts[] | .app // empty | .[]" \ - | xargs -I {} sh -c 'test -d "/Applications/{}" && echo "{}"' || true) + | xargs -I {} sh -c 'test -d "/Applications/$1" && echo "$1"' _ {} || true) if [[ -n "${existing_apps}" ]]; then echo -e "\033[33mfound, uninstalling ...\033[0m" brew uninstall --cask "${cask}" @@ -133,7 +144,7 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then # Accept Xcode license if command -v xcodebuild &> /dev/null; then if ! sudo -n true &> /dev/null; then - echo -e "\033[1;33mWARN:\033[0m you may be asked for your password to run 'xcodebuild -license'\n" + echo -e "\033[1;33mWARN:\033[0m you may be asked for your password to run 'xcodebuild -license'" fi sudo xcodebuild -license status || sudo xcodebuild -license accept || true fi diff --git a/settings.sh b/settings.sh index 8aed3a5..9e2e574 100755 --- a/settings.sh +++ b/settings.sh @@ -14,8 +14,8 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then if ! command -v plistwatch &> /dev/null && command -v go &> /dev/null; then plistwatch() { go install github.com/catilac/plistwatch@latest - unset -f "$0" - $0 "$@" + unset -f plistwatch + plistwatch "$@" } fi @@ -101,6 +101,7 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then # ***** Settings > Spotlight ***** # Disable Spotlight indexing on external volumes while read -r volume; do + [[ -z "${volume}" ]] && continue if mdutil -s "${volume}" | grep -xq "enabled"; then sudo mdutil -i off -d "${volume}" sudo killall DesktopServicesHelper @@ -133,7 +134,7 @@ if [[ "${OSTYPE:-}" == "darwin"* ]]; then # defaults -currentHost write com.apple.screensaver idleTime -int 1800 # defaults write com.apple.screensaver askForPasswordDelay -int 0 # Start screen saver when inactive for X seconds - defaults -currentHost write com.apple.screensaver -int 300 + defaults -currentHost write com.apple.screensaver idleTime -int 300 # Turn display off on battery when inctive for X minutes sudo pmset -b displaysleep 10 # Turn display off on power adapter when inctive for X minutes From f4348de281a32b5afc5f233a1655b0b057296428 Mon Sep 17 00:00:00 2001 From: Christian Emmer <10749361+emmercm@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:34:50 -0700 Subject: [PATCH 2/2] Fix --- dotpack/.30_claude.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotpack/.30_claude.sh b/dotpack/.30_claude.sh index 0283e1d..54aef52 100644 --- a/dotpack/.30_claude.sh +++ b/dotpack/.30_claude.sh @@ -20,7 +20,7 @@ __claude_auto_update() { if command -v claude &> /dev/null; then claude() { # Commands to skip updating before - if [[ " $@ " = *" -h "* || " $@ " == *" --help "* || " $@ " == *" -p "* || " $@ " == *" --print "* ]]; then + if [[ " $* " = *" -h "* || " $* " == *" --help "* || " $* " == *" -p "* || " $* " == *" --print "* ]]; then command claude "$@" return $? fi