From 749c5acf275d08fd8e0227e5555d8f87fb710afb Mon Sep 17 00:00:00 2001 From: Matthew Bessette Date: Fri, 30 Oct 2026 10:56:36 -0400 Subject: [PATCH] [cleo] stuff --- claude/CLAUDE.md | 2 +- claude/skills/prod-db/prod-query.sh | 2 +- claude/skills/redis/SKILL.md | 33 ++++++++++++ cleo.darwin.nix | 79 ++++++++++++++++++++++++++++- cleo.home.nix | 61 +++++++++++++++++----- home.shared.nix | 36 ++++++++++++- hosts | 15 ++++++ 7 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 claude/skills/redis/SKILL.md create mode 100644 hosts diff --git a/claude/CLAUDE.md b/claude/CLAUDE.md index b25984b..3b9eb46 100644 --- a/claude/CLAUDE.md +++ b/claude/CLAUDE.md @@ -5,7 +5,7 @@ - Anything mentioning Retool refers to `~/Projects/cleo-retool`. ## Worktrees -- Never write or edit code in the main checkouts (`~/Projects/cleo-infinite`, `~/Projects/cleo-retool`). Work in a treehouse worktree under `~/.treehouse/-//`. Check with `git rev-parse --show-toplevel` and `git worktree list`. +- Never write or edit code in the main checkouts (`~/Projects/cleo-infinite`, `~/Projects/cleo-retool`). Work in a treehouse worktree under `~/.local/share/treehouse/-//`. Check with `git rev-parse --show-toplevel` and `git worktree list`. - If code changes are needed and the current directory is not a treehouse worktree, start one yourself without asking first, then continue there. Read-only investigation can stay where it is. - Treehouse is a zsh alias for `nix run github:kunchenguid/treehouse --`, so it is not on PATH in non-interactive shells. Run the full `nix run` form, and run it with `--help` first rather than guessing subcommands or flags. - Branch names follow the repo's own rules (cleo-infinite: `-short_slug` from latest `origin/main`). Take the Jira key from the user or the current branch. If there isn't one, ask. Never invent a key. diff --git a/claude/skills/prod-db/prod-query.sh b/claude/skills/prod-db/prod-query.sh index e995bf7..1573c16 100755 --- a/claude/skills/prod-db/prod-query.sh +++ b/claude/skills/prod-db/prod-query.sh @@ -7,7 +7,7 @@ set -euo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROFILE="prod-mono-reader" +PROFILE="production.rds-reader" REGION="us-east-1" DB_USER="reader" MAX_ROWS=200 diff --git a/claude/skills/redis/SKILL.md b/claude/skills/redis/SKILL.md new file mode 100644 index 0000000..9a07c91 --- /dev/null +++ b/claude/skills/redis/SKILL.md @@ -0,0 +1,33 @@ +--- +name: redis +description: >- + Inspect Cleo ElastiCache (Valkey/Redis) caches through a local read-only proxy. + Use when the user asks to look at, list, or check Redis/Valkey/cache keys, + TTLs, sizes, or key counts for a service in dev or prod. +user-invocable: true +argument-hint: '[service and question about cache contents]' +--- +# redis + +`~/Projects/redis-proxy/redis-proxy` finds the cluster, reads its AUTH token from SSM, and listens on `127.0.0.1` with no auth. TLS and AUTH happen upstream, so never handle a token or use `rediss://`. Only read commands are forwarded; anything else returns `-ERR redis-proxy: blocked (read-only)`. + +```sh +~/Projects/redis-proxy/redis-proxy list --env dev|prod +~/Projects/redis-proxy/redis-proxy up --env dev|prod [--port 16379] [--primary] # run with run_in_background +valkey-cli -p 16379 +``` + +Wait for `listening on 127.0.0.1:` in the background output before connecting. Stop the proxy when done. If it reports no SSO session, ask the user to run the `! aws sso login --profile ` command it prints. + +## PII/PHI rules (org policy: never put PII/PHI into Claude) + +- Prefer `SCAN ... COUNT`, `TYPE`, `TTL`, `STRLEN`, `MEMORY USAGE`, `DBSIZE`, `INFO keyspace` over reading values. Avoid `KEYS`. +- Key names can contain emails or IDs. Don't repeat a key that looks like an email in output; refer to "the key". +- In prod, don't `GET`/`HGETALL`/`LRANGE` etc. on keys that look like sessions, users, messages, or profiles unless the user explicitly asks, and then summarize shape (field names, sizes) rather than pasting values. +- Never read or print the SSM token. + +## Notes + +- The proxy uses the reader endpoint by default; `--primary` switches. Dev can pass `--allow-writes`; prod refuses it. +- Default port is 16379. Pass `--port` to run several proxies side by side. +- Logs show blocked command names only. diff --git a/cleo.darwin.nix b/cleo.darwin.nix index 139bb6b..a14d240 100644 --- a/cleo.darwin.nix +++ b/cleo.darwin.nix @@ -3,11 +3,29 @@ , ... }: let + # Managed block in /etc/hosts. Same markers the ansible blockinfile uses, so + # an existing block is rewritten in place rather than duplicated. + cleoHosts = [ + "dev.wellthy.com" + "www.dev.wellthy.com" + "app.dev.wellthy.com" + "www.app.dev.wellthy.com" + "auth.dev.wellthy.com" + "internal-api.dev.wellthy.com" + "local-api.hicleo.com" + "local-identity.hicleo.com" + "local-file-ingestion.hicleo.com" + "local-eligibility.hicleo.com" + ]; + cleoHostsBlock = pkgs.writeText "cleo-local-dev-hosts" ( + lib.concatMapStrings (h: "127.0.0.1 ${h}\n") cleoHosts + ); ansibleRequired = { taps = [ "fwdcloudsec/granted" # aws sso utility "felipeelias/tap" # claude-statusline "oven-sh/bun" # bun + "schpet/tap" ## Excluded intentionally # "sst/tap" # opencode @@ -17,18 +35,20 @@ let "aws-sso-util" "awscli" "ccusage" + "doppler" "fd" "felipeelias/tap/claude-statusline" "oven-sh/bun/bun" "fwdcloudsec/granted/granted" + "schpet/tap/linear" "gh" "jq" "maven" - "rg" "snyk-cli" "ldcli" ## Excluded intentionally + # "rg" # "gdal" # "sst/tap/opencode" # "libpq@17" @@ -60,6 +80,15 @@ in environment.variables.GIT_EDITOR = "vim"; environment.variables.HOMEBREW_NO_ANALYTICS = "1"; + # GUI apps (Android Studio, Dock-launched tools) don't see home-manager session + # vars, so mirror the XDG-relocated ones here with absolute paths. + launchd.user.envVariables = { + CLAUDE_CONFIG_DIR = "/Users/matthew.bessette/.config/claude"; + GRADLE_USER_HOME = "/Users/matthew.bessette/.local/share/gradle"; + ANDROID_USER_HOME = "/Users/matthew.bessette/.local/share/android"; + CP_HOME_DIR = "/Users/matthew.bessette/.local/share/cocoapods"; + }; + environment.systemPackages = with pkgs; [ vim git @@ -67,6 +96,18 @@ in curl python3 postgresql_17 + # valkey-cli for the ~/Projects/redis-proxy ElastiCache proxy + valkey + # wellthy.com: psycopg2 builds from source (needs pg_config + libpq headers) + postgresql_17.pg_config + postgresql_17.dev + # wellthy.com: python-magic loads libmagic; gettext for compilemessages + file + gettext + openssl + openssl.dev + # default outputs are bin+man only; the shared libs live in `out` + openssl.out home-manager ttyd xcodes @@ -80,6 +121,42 @@ in act ]; + # Link lib/ and include/ into /run/current-system/sw so native builds (psycopg2) + # and runtime lookups (python-magic -> libmagic) have a stable path. + environment.pathsToLink = [ "/lib" "/include" ]; + + # python-magic only searches /opt/local/lib, /usr/local/lib and /opt/homebrew/lib + # (DYLD_* variables don't survive macOS system binaries). Point the last one at the + # nix libmagic through the stable system profile path. Only touches the path when + # it is free or already our own link, so a Homebrew libmagic is never overwritten. + system.activationScripts.postActivation.text = '' + dest=/opt/homebrew/lib/libmagic.dylib + ours=/run/current-system/sw/lib/libmagic.dylib + if [ -d /opt/homebrew/lib ] && { { [ ! -e "$dest" ] && [ ! -L "$dest" ]; } || [ "$(readlink "$dest")" = "$ours" ]; }; then + ln -sfn "$ours" "$dest" + fi + + # /etc/hosts is owned by macOS (and holds hand-written entries), so manage only + # the CLEO LOCAL DEV HOSTS block: replace it in place, or append it if missing. + hosts=/etc/hosts + begin="# BEGIN CLEO LOCAL DEV HOSTS" + end="# END CLEO LOCAL DEV HOSTS" + tmp=$(mktemp) + if grep -qxF "$begin" "$hosts"; then + awk -v begin="$begin" -v end="$end" -v blk="${cleoHostsBlock}" ' + $0 == begin { print; while ((getline l < blk) > 0) print l; skip = 1; next } + $0 == end { skip = 0 } + !skip { print } + ' "$hosts" > "$tmp" + else + { cat "$hosts"; printf '\n%s\n' "$begin"; cat ${cleoHostsBlock}; printf '%s\n' "$end"; } > "$tmp" + fi + if ! cmp -s "$tmp" "$hosts"; then + cat "$tmp" > "$hosts" + fi + rm -f "$tmp" + ''; + # Add Homebrew to PATH environment.systemPath = [ "/opt/homebrew/bin" diff --git a/cleo.home.nix b/cleo.home.nix index f0fbd12..7e193c8 100644 --- a/cleo.home.nix +++ b/cleo.home.nix @@ -1,4 +1,4 @@ -{ config, pkgs, vars, ... }: +{ config, lib, pkgs, vars, ... }: let # Agent/skill bodies live in the cleo-infinite checkout; symlinked so they # follow upstream on every pull. @@ -10,7 +10,10 @@ in ./programs.vscode.nix ]; - home.packages = with pkgs; [ ]; + home.packages = with pkgs; [ + mkcert + nginx + ]; programs.mise = { enable = true; @@ -56,11 +59,24 @@ in CLAUDE_CONFIG_DIR = "${config.home.homeDirectory}/.config/claude"; # Points to the Homebrew location mentioned in your caveat JAVA_HOME = "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home"; - NPM_CONFIG_PREFIX = "${config.home.homeDirectory}/.npm-global"; + NPM_CONFIG_USERCONFIG = "${config.xdg.configHome}/npm/npmrc"; + NPM_CONFIG_PREFIX = "${config.xdg.dataHome}/npm"; GRANTED_ALIAS_CONFIGURED = "true"; # Stub SDK root so tools that require ANDROID_HOME/platform-tools/adb work # even though adb is managed by Homebrew (android-platform-tools cask). - ANDROID_HOME = "${config.home.homeDirectory}/android-sdk"; + ANDROID_HOME = "${config.xdg.dataHome}/android-sdk"; + ANDROID_USER_HOME = "${config.xdg.dataHome}/android"; + GRADLE_USER_HOME = "${config.xdg.dataHome}/gradle"; + CP_HOME_DIR = "${config.xdg.dataHome}/cocoapods"; + # Ruby comes from mise, so GEM_HOME is left alone (mise keeps per-version gem dirs). + GEM_SPEC_CACHE = "${config.xdg.cacheHome}/gem/specs"; + BUNDLE_USER_CONFIG = "${config.xdg.configHome}/bundle/config"; + BUNDLE_USER_CACHE = "${config.xdg.cacheHome}/bundle"; + BUNDLE_USER_PLUGIN = "${config.xdg.dataHome}/bundle"; + TREEHOUSE_ROOT = "${config.xdg.dataHome}/treehouse"; + # mkcert CA and leaf certs for local dev TLS (tasks/config/dev-certs.yml). + CAROOT = "${config.xdg.dataHome}/mkcert"; + DEV_CERTS_DIR = "${config.xdg.dataHome}/devcerts"; # Runtime lookup from Apple Keychain — only the command, not the token, is in the store. JIRA_API_TOKEN = ''$(security find-generic-password -a "$USER" -s jira-token -w 2>/dev/null)''; @@ -73,21 +89,37 @@ in COPILOT_OTEL_FILE_EXPORTER_PATH = ''$HOME/.copilot/otel/copilot-otel-$(date +%Y%m%d-%H%M%S).jsonl''; }; + # Share one [sso-session] across the aws-sso-util profiles so `aws` and + # Granted's `assume` stay in sync (tasks/aws.yml). ~/.aws/config stays + # mutable because aws-sso-util rewrites it, so this converts it in place. + home.activation.awsSsoSession = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + script="${ansibleFiles}/aws-sso-session.py" + if [ -f "$script" ] && [ -f "$HOME/.aws/config" ]; then + run ${pkgs.python3}/bin/python3 -I "$script" || [ $? -eq 3 ] + fi + ''; + home.sessionPath = [ - "${config.home.homeDirectory}/.npm-global/bin" - "${config.home.homeDirectory}/.local/share/mise/shims" + "${config.xdg.dataHome}/npm/bin" + "${config.xdg.dataHome}/mise/shims" ]; # git credential.helper=osxkeychain (parity with tasks/git.yml). Darwin-only, so # it lives here rather than in the cross-platform home.shared.nix. - programs.git.settings.credential.helper = "osxkeychain"; + programs.git.settings.credential = { + helper = "osxkeychain"; + # Moved from the stray ~/.gitconfig; the empty entry resets the helper list for these hosts. + "https://github.com".helper = [ "" "!/opt/homebrew/bin/gh auth git-credential" ]; + "https://gist.github.com".helper = [ "" "!/opt/homebrew/bin/gh auth git-credential" ]; + }; - home.file.".npmrc".text = '' - prefix=${config.home.homeDirectory}/.npm-global + xdg.configFile."npm/npmrc".text = '' + prefix=${config.xdg.dataHome}/npm + cache=${config.xdg.cacheHome}/npm ''; - home.file."android-sdk/platform-tools/adb".source = config.lib.file.mkOutOfStoreSymlink "/opt/homebrew/bin/adb"; - home.file."android-sdk/cmdline-tools/latest/bin/sdkmanager".source = config.lib.file.mkOutOfStoreSymlink "/opt/homebrew/bin/sdkmanager"; + xdg.dataFile."android-sdk/platform-tools/adb".source = config.lib.file.mkOutOfStoreSymlink "/opt/homebrew/bin/adb"; + xdg.dataFile."android-sdk/cmdline-tools/latest/bin/sdkmanager".source = config.lib.file.mkOutOfStoreSymlink "/opt/homebrew/bin/sdkmanager"; home.file.".ssh/authorized_keys".text = (builtins.concatStringsSep "\n" vars.sshPublicKeys) + "\n"; @@ -118,6 +150,8 @@ in config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nixos/claude/CLAUDE.md"; xdg.configFile."claude/skills/prod-db".source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nixos/claude/skills/prod-db"; + xdg.configFile."claude/skills/redis".source = + config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nixos/claude/skills/redis"; # opencode (tasks/config/opencode.yml) xdg.configFile."opencode/opencode.json".text = builtins.toJSON { @@ -140,6 +174,9 @@ in }; xdg.configFile."treehouse/config.toml".text = '' [hooks] - post_create = ["./conditional-package-manage.sh"] + post_create = [ + "if [ \"$(basename \"$PWD\")\" = \"wellthy.com\" ]; then (git diff HEAD..origin/cleo-native-local-patch | git apply --3way 2>/dev/null || true) && git status --porcelain | awk '{print $2}' >> \"$(git rev-parse --git-path info/exclude)\" && git status --porcelain | awk '{print $2}' | xargs -r git update-index --skip-worktree; fi", + "./conditional-package-manage.sh" + ] ''; } diff --git a/home.shared.nix b/home.shared.nix index 27f88ac..19b0ec7 100644 --- a/home.shared.nix +++ b/home.shared.nix @@ -23,6 +23,13 @@ in home.sessionVariables.SSH_AUTH_SOCK = onePassPath; home.sessionVariables.CLAUDE_CONFIG_DIR = "${config.xdg.configHome}/claude"; + # Tools that only take a history file path via env var. + home.sessionVariables.LESSHISTFILE = "${config.xdg.stateHome}/less/history"; + home.sessionVariables.NODE_REPL_HISTORY = "${config.xdg.stateHome}/node_repl_history"; + home.sessionVariables.PYTHON_HISTORY = "${config.xdg.stateHome}/python_history"; + home.sessionVariables.PSQL_HISTORY = "${config.xdg.stateHome}/psql_history"; + home.sessionVariables.IPYTHONDIR = "${config.xdg.configHome}/ipython"; + home.packages = with pkgs; [ nixd treefmt @@ -36,13 +43,26 @@ in xdg.configHome = "${config.home.homeDirectory}/.config"; xdg.cacheHome = "${config.home.homeDirectory}/.cache"; xdg.dataHome = "${config.home.homeDirectory}/.local/share"; - xdg.stateHome = "${config.home.homeDirectory}/.config/state"; + xdg.stateHome = "${config.home.homeDirectory}/.local/state"; xdg.configFile."1Password/ssh/agent.toml".text = '' [[ssh-keys]] item = "${userConfig._1PassKeyName}" vault = "${userConfig._1PassKeyVault}" ''; + # less and vim don't create the parent dir of their history/viminfo files. + xdg.stateFile."less/.keep".text = ""; + xdg.stateFile."vim/.keep".text = ""; + + # vim >= 9.1 reads ~/.config/vim/vimrc when ~/.vimrc and ~/.vim are absent. + xdg.configFile."vim/vimrc".text = '' + unlet! skip_defaults_vim + source $VIMRUNTIME/defaults.vim + + set viminfofile=${config.xdg.stateHome}/vim/viminfo + let g:netrw_home = '${config.xdg.stateHome}/vim' + ''; + my.claude.settings = { "$schema" = "https://json.schemastore.org/claude-code-settings.json"; theme = "dark"; @@ -70,6 +90,18 @@ in format = 'S: {{printf "%.0f" .BlockPct}}% ({{.BlockResets}}) | W: {{printf "%.0f" .WeeklyPct}}%' ''; + programs.zoxide = { + enable = true; + enableZshIntegration = true; + }; + + programs.fzf = { + enable = true; + enableZshIntegration = true; + }; + + programs.ripgrep.enable = true; + programs.tmux.enable = true; programs.fastfetch.enable = true; @@ -78,6 +110,7 @@ in enableCompletion = true; autosuggestion.enable = true; syntaxHighlighting.enable = true; + history.path = "${config.xdg.stateHome}/zsh/history"; shellAliases = { ll = "ls -l"; @@ -182,6 +215,7 @@ in enableBashIntegration = true; enableZshIntegration = true; settings = { + command = "tmux new-session -A -s main"; shell-integration-features = "ssh-terminfo,ssh-env"; macos-icon = "chalkboard"; keybind = [ diff --git a/hosts b/hosts new file mode 100644 index 0000000..14fe770 --- /dev/null +++ b/hosts @@ -0,0 +1,15 @@ +## +# Host Database +# +# localhost is used to configure the loopback interface +# when the system is booting. Do not change this entry. +## +127.0.0.1 localhost +255.255.255.255 broadcasthost +::1 localhost + + +# 10.80.7.79 app.hicleo.com +# 10.80.8.132 app.hicleo.com +# 10.80.6.75 app.hicleo.com +192.168.1.45 cloud.thiccdata.io