From e1c7e16cf6a927831c66474457fab14b172164f1 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Sun, 8 Mar 2026 15:44:43 -0500 Subject: [PATCH 01/14] fix(macos): harden bootstrap and automation roles --- group_vars/all.yml | 2 +- pre_tasks/detect_1password.yml | 27 ++++-- roles/1password/tasks/MacOSX.yml | 33 +++++++ roles/aldente/tasks/MacOSX.yml | 2 +- roles/bash/files/os/MacOSX/zz_os_functions.sh | 4 +- roles/borders/handlers/main.yml | 4 + roles/borders/tasks/MacOSX.yml | 3 +- roles/git/tasks/main.yml | 85 +++++++++++-------- roles/hammerspoon/README.md | 8 +- roles/hammerspoon/files/config/chain.lua | 1 + roles/hammerspoon/files/config/init.lua | 74 ++++++++++++---- roles/hammerspoon/files/config/summon.lua | 75 +++++++++++----- roles/hammerspoon/tasks/MacOSX.yml | 73 ++++++---------- roles/hammerspoon/tasks/spoons.yml | 20 +++-- roles/jj/tasks/main.yml | 4 +- roles/ssh/tasks/main.yml | 9 +- roles/zsh/files/os/MacOSX/os_functions.zsh | 4 +- 17 files changed, 282 insertions(+), 146 deletions(-) create mode 100644 roles/borders/handlers/main.yml diff --git a/group_vars/all.yml b/group_vars/all.yml index f1086058..b908604c 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -1,6 +1,6 @@ default_roles: - system - # - 1password + - 1password - aldente - asciiquarium - awesomewm diff --git a/pre_tasks/detect_1password.yml b/pre_tasks/detect_1password.yml index c31e1ff8..50faaaf0 100644 --- a/pre_tasks/detect_1password.yml +++ b/pre_tasks/detect_1password.yml @@ -1,12 +1,29 @@ --- -- name: Detect 1Password +- name: Initialize 1Password facts + ansible.builtin.set_fact: + op_installed: false + op_authenticated: false + +- name: Detect 1Password CLI ansible.builtin.command: cmd: which op changed_when: false failed_when: false - register: op_installed + register: op_binary_check + +- name: Register 1Password installation state + ansible.builtin.set_fact: + op_installed: "{{ op_binary_check.rc == 0 }}" + +- name: Detect 1Password authentication state + ansible.builtin.command: + cmd: op whoami + changed_when: false + failed_when: false + register: op_whoami + when: op_installed -- name: Register 1Password +- name: Register 1Password authentication state ansible.builtin.set_fact: - op_installed: "{{ op_installed.rc == 0 }}" - when: op_installed.rc == 0 + op_authenticated: "{{ op_whoami.rc == 0 }}" + when: op_installed diff --git a/roles/1password/tasks/MacOSX.yml b/roles/1password/tasks/MacOSX.yml index 1590bdbc..5c31506d 100644 --- a/roles/1password/tasks/MacOSX.yml +++ b/roles/1password/tasks/MacOSX.yml @@ -6,3 +6,36 @@ loop: - 1password - 1password-cli + +- name: "1Password | MacOSX | Detect 1Password CLI after install" + ansible.builtin.command: + cmd: which op + changed_when: false + failed_when: false + register: op_binary_check + +- name: "1Password | MacOSX | Register CLI availability" + ansible.builtin.set_fact: + op_installed: "{{ op_binary_check.rc == 0 }}" + +- name: "1Password | MacOSX | Detect authentication state" + ansible.builtin.command: + cmd: op whoami + changed_when: false + failed_when: false + register: op_whoami + when: op_installed + +- name: "1Password | MacOSX | Register authentication state" + ansible.builtin.set_fact: + op_authenticated: "{{ op_whoami.rc == 0 }}" + when: op_installed + +- name: "1Password | MacOSX | Explain next step when not authenticated" + ansible.builtin.debug: + msg: + - "1Password is installed, but the CLI is not authenticated yet." + - "Open 1Password, sign in or unlock it, then rerun: dotfiles -t 1password,ssh,git,jj" + when: + - op_installed + - not (op_authenticated | default(false)) diff --git a/roles/aldente/tasks/MacOSX.yml b/roles/aldente/tasks/MacOSX.yml index c7fafdc8..053681b1 100644 --- a/roles/aldente/tasks/MacOSX.yml +++ b/roles/aldente/tasks/MacOSX.yml @@ -1,5 +1,5 @@ --- - name: "Aldente | MacOSX | Install Aldente" - community.general.homebrew: + community.general.homebrew_cask: name: aldente state: present diff --git a/roles/bash/files/os/MacOSX/zz_os_functions.sh b/roles/bash/files/os/MacOSX/zz_os_functions.sh index c2795594..5ee94d6c 100644 --- a/roles/bash/files/os/MacOSX/zz_os_functions.sh +++ b/roles/bash/files/os/MacOSX/zz_os_functions.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash alias update='brew update && brew upgrade && brew cleanup' -export SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock +if [ -S ~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock ]; then + export SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock +fi diff --git a/roles/borders/handlers/main.yml b/roles/borders/handlers/main.yml new file mode 100644 index 00000000..a3179554 --- /dev/null +++ b/roles/borders/handlers/main.yml @@ -0,0 +1,4 @@ +--- +- name: "borders | macOS | Restart borders service" + ansible.builtin.command: brew services restart borders + changed_when: true diff --git a/roles/borders/tasks/MacOSX.yml b/roles/borders/tasks/MacOSX.yml index c170205c..5b342ac0 100644 --- a/roles/borders/tasks/MacOSX.yml +++ b/roles/borders/tasks/MacOSX.yml @@ -21,6 +21,7 @@ dest: "{{ ansible_facts['env']['HOME'] }}/.config/borders/bordersrc" mode: '0755' backup: yes + notify: "borders | macOS | Restart borders service" - name: "{{ role_name }} | macOS | Check if borders service is running" ansible.builtin.command: brew services list @@ -30,4 +31,4 @@ - name: "{{ role_name }} | macOS | Start borders service" ansible.builtin.command: brew services start borders when: "'borders stopped' in brew_services.stdout or 'borders none' in brew_services.stdout" - changed_when: true \ No newline at end of file + changed_when: true diff --git a/roles/git/tasks/main.yml b/roles/git/tasks/main.yml index 90aa7254..356effb1 100644 --- a/roles/git/tasks/main.yml +++ b/roles/git/tasks/main.yml @@ -64,7 +64,7 @@ value: true - name: "Git | Read user.email secret from 1Password" - when: op_installed + when: op_authenticated | default(false) block: - name: "1Password | Get user.email" ansible.builtin.command: "op --account my.1password.com read '{{ op.git.user.email }}'" @@ -79,16 +79,12 @@ no_log: true when: op_git_user_email.rc == 0 - - name: "1Password | Warning when not authenticated" - ansible.builtin.debug: - msg: - - "⚠️ 1Password CLI is installed but not authenticated." - - "" - - "To fix this, run:" - - "eval $(op signin)" - - "" - - "Then run dotfiles again. For now, skipping 1Password integration." - when: op_git_user_email.rc != 0 +- name: "Git | Warning - 1Password not authenticated" + ansible.builtin.debug: + msg: "Git secret sync skipped because 1Password is installed but not authenticated. Unlock 1Password, then rerun dotfiles -t 1password,git." + when: + - op_installed | default(false) + - not (op_authenticated | default(false)) - name: "Git | Set user.email" community.general.git_config: @@ -104,29 +100,42 @@ scope: global value: "{{ git_user_name }}" -- name: "Git | Set user.signingkey" - community.general.git_config: - name: user.signingkey - scope: global - value: "{{ ansible_facts['user_dir'] }}/.ssh/id_ed25519.pub" - -- name: "Git | Set gpg.format" - community.general.git_config: - name: gpg.format - scope: global - value: ssh - -- name: "Git | Set commit.gpgsign" - community.general.git_config: - name: commit.gpgsign - scope: global - value: true +- name: "Git | Detect SSH signing key" + ansible.builtin.stat: + path: "{{ ansible_facts['user_dir'] }}/.ssh/id_ed25519.pub" + register: git_signing_key -- name: "Git | Set tag.gpgsign" - community.general.git_config: - name: tag.gpgsign - scope: global - value: true +- name: "Git | Configure SSH signing" + when: git_signing_key.stat.exists + block: + - name: "Git | Set user.signingkey" + community.general.git_config: + name: user.signingkey + scope: global + value: "{{ ansible_facts['user_dir'] }}/.ssh/id_ed25519.pub" + + - name: "Git | Set gpg.format" + community.general.git_config: + name: gpg.format + scope: global + value: ssh + + - name: "Git | Set commit.gpgsign" + community.general.git_config: + name: commit.gpgsign + scope: global + value: true + + - name: "Git | Set tag.gpgsign" + community.general.git_config: + name: tag.gpgsign + scope: global + value: true + +- name: "Git | Warn when SSH signing key is unavailable" + ansible.builtin.debug: + msg: "Git SSH signing skipped for now because ~/.ssh/id_ed25519.pub is not present. Unlock 1Password and rerun dotfiles -t 1password,ssh,git." + when: not git_signing_key.stat.exists - name: "Git | Ensure ~/.config/git/allowed_signers exists" ansible.builtin.file: @@ -140,7 +149,7 @@ changed_when: false failed_when: false no_log: true - when: op_installed + when: op_authenticated | default(false) - name: "1Password | Configure ~/.config/git/allowed_signers" ansible.builtin.blockinfile: @@ -150,14 +159,20 @@ create: true no_log: true when: - - op_installed + - op_authenticated | default(false) - op_git_ssh_allowed_signers.rc == 0 +- name: "Git | Detect allowed_signers file" + ansible.builtin.stat: + path: "{{ ansible_facts['user_dir'] }}/.config/git/allowed_signers" + register: git_allowed_signers_file + - name: "Git | Set gpg.ssh.allowedSignersFile" community.general.git_config: name: gpg.ssh.allowedSignersFile scope: global value: "{{ ansible_facts['user_dir'] }}/.config/git/allowed_signers" + when: git_allowed_signers_file.stat.exists - name: "Git | Set undo alias" community.general.git_config: diff --git a/roles/hammerspoon/README.md b/roles/hammerspoon/README.md index 3f01b3e1..7ba9bb25 100644 --- a/roles/hammerspoon/README.md +++ b/roles/hammerspoon/README.md @@ -19,8 +19,10 @@ Hammerspoon is a powerful macOS automation tool that bridges the gap between the - **Hammerspoon** - Installed via Homebrew Cask ### Spoons (Extensions) -- **GridLayout.spoon** - Custom grid-based layout manager (commented out, using custom implementation) -- **ReloadConfiguration.spoon** - Auto-reload on config changes +- **GridLayout.spoon** - Custom grid-based layout manager installed into `~/.hammerspoon/Spoons/` + +### Native Hammerspoon Features +- **`hs.pathwatcher`** - Auto-reload on config changes without an extra spoon ## ⚙️ What Gets Configured @@ -167,7 +169,7 @@ Inspired by Slate's chain feature with enhancements: - Always starts on current screen ### Auto-Reload -Configuration automatically reloads when files change in `~/.hammerspoon/`. +Configuration automatically reloads when `.lua` files change in `~/.hammerspoon/` via Hammerspoon's native `hs.pathwatcher`. ## 🖼️ Visual Architecture diff --git a/roles/hammerspoon/files/config/chain.lua b/roles/hammerspoon/files/config/chain.lua index 39b5ba33..f51a8647 100644 --- a/roles/hammerspoon/files/config/chain.lua +++ b/roles/hammerspoon/files/config/chain.lua @@ -13,6 +13,7 @@ local lastSeenChain = nil local lastSeenWindow = nil +local lastSeenAt = 0 return (function(movements) local chainResetInterval = 2 -- seconds diff --git a/roles/hammerspoon/files/config/init.lua b/roles/hammerspoon/files/config/init.lua index c1141207..718b89bc 100644 --- a/roles/hammerspoon/files/config/init.lua +++ b/roles/hammerspoon/files/config/init.lua @@ -5,7 +5,22 @@ lilHyper = { 'cmd', 'alt', 'ctrl' } -- or D+F 🤘 Hyper = { 'shift', 'cmd', 'alt', 'ctrl' } -- or S+D+F 😅 -hs.loadSpoon('ReloadConfiguration'):start() +local function reloadConfig(files) + local shouldReload = false + + for _, file in ipairs(files) do + if file:match('%.lua$') then + shouldReload = true + break + end + end + + if shouldReload then + hs.reload() + end +end + +configWatcher = hs.pathwatcher.new(os.getenv('HOME') .. '/.hammerspoon/', reloadConfig):start() require('helpers') @@ -73,23 +88,52 @@ local layout = hs.loadSpoon('GridLayout') :setGrid(positions.full_grid) :setMargins('5x5') -if (hs.screen.primaryScreen():name() ~= 'Built-in Retina Display') then - layout:selectLayout(1) +local function isBuiltInDisplay(screen) + local name = ((screen and screen:name()) or ''):lower() + return name:find('built%-in', 1, false) ~= nil end -hs.screen.watcher.new(function() - hs.timer.doAfter(1, function() - local screen = hs.screen.primaryScreen() - local mode = screen:currentMode() - - if screen:name() == 'Built-in Retina Display' then - layout:selectLayout(2) -- Fullscreen for laptop - elseif mode.w >= 3840 then - layout:selectLayout(1) -- 4K Workspace - else - layout:selectLayout(4) -- Standard Dev - end +local function selectDefaultLayout() + local screen = hs.screen.primaryScreen() + if not screen then + return + end + + local mode = screen:currentMode() + local aspectRatio = mode.w / math.max(mode.h, 1) + local layoutIndex + + if isBuiltInDisplay(screen) then + layoutIndex = 2 -- Fullscreen for the internal display + elseif mode.w >= 5000 or aspectRatio >= 2.8 then + layoutIndex = 4 -- Ultrawide workspace + elseif mode.w >= 3840 or mode.h >= 2160 then + layoutIndex = 1 -- 4K workspace + else + layoutIndex = 3 -- Standard external monitor + end + + layout:selectLayout(layoutIndex) +end + +local pendingLayoutSelection = nil + +local function scheduleLayoutSelection(delaySeconds) + if pendingLayoutSelection then + pendingLayoutSelection:stop() + pendingLayoutSelection = nil + end + + pendingLayoutSelection = hs.timer.doAfter(delaySeconds or 0, function() + pendingLayoutSelection = nil + selectDefaultLayout() end) +end + +scheduleLayoutSelection(0) + +hs.screen.watcher.new(function() + scheduleLayoutSelection(1) end):start() local windowManagementBindings = { diff --git a/roles/hammerspoon/files/config/summon.lua b/roles/hammerspoon/files/config/summon.lua index 8dc8b39f..2ded3158 100644 --- a/roles/hammerspoon/files/config/summon.lua +++ b/roles/hammerspoon/files/config/summon.lua @@ -1,36 +1,67 @@ --- Simplified summon with reliable toggle and proper activation local previousApp = nil +local currentApp = nil + +local function appIdentity(app) + if not app then + return nil + end + + return app:bundleID() or app:name() +end + +local function resolveApp(identifier) + if not identifier then + return nil + end + + return hs.application.get(identifier) or hs.application.find(identifier) +end + +local function appMatches(app, identifier, fallbackName) + if not app then + return false + end + + local bundleId = app:bundleID() + local name = app:name() + + return bundleId == identifier or name == identifier or bundleId == fallbackName or name == fallbackName +end --- Track what we switch TO (more reliable than tracking what we leave) hs.window.filter.new():subscribe(hs.window.filter.windowFocused, function(win) - if win and win:application() then - local currentApp = hs.application.frontmostApplication():bundleID() - if currentApp ~= previousApp then - previousApp = currentApp - end + local app = win and win:application() + local identity = appIdentity(app) + + if not identity or identity == currentApp then + return + end + + if currentApp then + previousApp = currentApp end + + currentApp = identity end) return function(appName) - -- Get app ID - local id = (_G.apps and _G.apps[appName] and _G.apps[appName].id) or appName - - local app = hs.application.find(id) - local currentId = hs.application.frontmostApplication():bundleID() - - -- Case 1: Target app is focused and we have history - toggle back - if currentId == id and previousApp and previousApp ~= id then - local prevApp = hs.application.find(previousApp) + local target = (_G.apps and _G.apps[appName]) or {} + local id = target.id or appName + local frontmostApp = hs.application.frontmostApplication() + local app = resolveApp(id) or resolveApp(appName) + + if appMatches(frontmostApp, id, appName) and previousApp and not appMatches(frontmostApp, previousApp, previousApp) then + local prevApp = resolveApp(previousApp) if prevApp then - prevApp:activate() -- Use activate for existing apps + prevApp:activate() else hs.application.open(previousApp) end - -- Case 2: App exists with windows - activate it (don't create new window) elseif app and next(app:allWindows()) then - app:activate() -- SECRET SAUCE: activate existing instead of open - -- Case 3: App not running or no windows - open it + app:activate() else - hs.application.open(id) + local opened = hs.application.open(id) + if not opened and appName ~= id then + hs.application.open(appName) + end end -end \ No newline at end of file +end diff --git a/roles/hammerspoon/tasks/MacOSX.yml b/roles/hammerspoon/tasks/MacOSX.yml index fc452a33..8a5a7a0c 100644 --- a/roles/hammerspoon/tasks/MacOSX.yml +++ b/roles/hammerspoon/tasks/MacOSX.yml @@ -27,58 +27,33 @@ state: directory mode: "0755" +- name: "Hammerspoon | MacOSX | Ensure ~/.hammerspoon/Spoons exists" + ansible.builtin.file: + path: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons" + state: directory + mode: "0755" + - name: "Hammerspoon | MacOSX | Deploy Hammerspoon Configuration" ansible.builtin.copy: src: "config/" dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon" mode: "0644" -# - name: "Hammerspoon | MacOSX | Detect local instance of GridLayout.spoon" -# ansible.builtin.stat: -# name: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon" -# register: hammerspoon_gridlayout_spoon -# -# - name: "Hammerspoon | MacOSX | Detect latest GridLayout.spoon Github release" -# ansible.builtin.uri: -# url: "https://api.github.com/repos/jesseleite/GridLayout.spoon/releases/latest" -# return_content: true -# register: hammerspoon_gridlayout_release -# -# - name: "Hammerspoon | MacOSX | Extract latest GridLayout.spoon release version" -# ansible.builtin.set_fact: -# hammerspoon_gridlayout_latest_version: "{{ hammerspoon_gridlayout_release.json.tag_name }}" -# when: hammerspoon_gridlayout_release.status == 200 -# -# - name: "Hammerspoon | MacOSX | Install latest GridLayout.spoon" -# when: not hammerspoon_gridlayout_spoon.stat.exists -# block: -# - name: "Hammerspoon | MacOSX | Ensure ~/.hammerspoon/Spoons exists" -# ansible.builtin.file: -# name: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons" -# state: directory -# mode: "0755" -# -# - name: "Hammerspoon | MacOSX | Download latest GridLayout.spoon" -# ansible.builtin.uri: -# url: "https://github.com/jesseleite/GridLayout.spoon/releases/download/{{ hammerspoon_gridlayout_latest_version }}/GridLayout.spoon.zip" -# dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons" -# notify: "Hammerspoon | MacOSX | Cleanup latest GridLayout.spoon zip" -# -# - name: "Hammerspoon | MacOSX | Ensure ~/.hammerspoon/Spoons/GridLayout.spoon exists" -# ansible.builtin.file: -# name: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon" -# state: directory -# mode: "0755" -# -# - name: "Hammerspoon | MacOSX | Unzip latest GridLayout.spoon" -# ansible.builtin.unarchive: -# src: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon.zip" -# dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon" -# creates: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon" -# -# - name: "Hammerspoon | MacOSX | Install Official Hammerspoon Spoons" -# ansible.builtin.include_tasks: spoons.yml -# loop: -# - "ReloadConfiguration" -# loop_control: -# loop_var: current_spoon +- name: "Hammerspoon | MacOSX | Download GridLayout.spoon" + ansible.builtin.get_url: + url: "https://github.com/jesseleite/GridLayout.spoon/releases/latest/download/GridLayout.spoon.zip" + dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon.zip" + mode: "0644" + +- name: "Hammerspoon | MacOSX | Ensure ~/.hammerspoon/Spoons/GridLayout.spoon exists" + ansible.builtin.file: + path: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon" + state: directory + mode: "0755" + +- name: "Hammerspoon | MacOSX | Install GridLayout.spoon" + ansible.builtin.unarchive: + src: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon.zip" + dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/GridLayout.spoon" + remote_src: true + mode: "0644" diff --git a/roles/hammerspoon/tasks/spoons.yml b/roles/hammerspoon/tasks/spoons.yml index 3b5d7f1e..63883921 100644 --- a/roles/hammerspoon/tasks/spoons.yml +++ b/roles/hammerspoon/tasks/spoons.yml @@ -1,23 +1,25 @@ --- - name: "Hammerspoon | MacOSX | {{ current_spoon }} | Detect local instance of {{ current_spoon }}" ansible.builtin.stat: - name: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/{{ current_spoon }}" + path: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/{{ current_spoon }}.spoon" register: hammerspoon_current_spoon - name: "Hammerspoon | MacOSX | {{ current_spoon }} | Download latest {{ current_spoon }}" when: not hammerspoon_current_spoon.stat.exists block: - name: "Hammerspoon | MacOSX | {{ current_spoon }} | Download latest {{ current_spoon }}" - ansible.builtin.uri: - url: "https://github.com/Hammerspoon/Spoons/blob/master/Spoons/{{ current_spoon }}.spoon.zip" - dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons" + ansible.builtin.get_url: + url: "https://raw.githubusercontent.com/Hammerspoon/Spoons/master/Spoons/{{ current_spoon }}.spoon.zip" + dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/{{ current_spoon }}.spoon.zip" + mode: "0644" - - name: "Hammerspoon | MacOSX | {{ current_spoon }} | Install latest {{ current_spoon }}" - ansible.builtin.command: - cmd: "open -a /Applications/Hammerspoon.app {{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/{{ current_spoon }}.spoon.zip" - creates: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/{{ current_spoon }}.spoon" + - name: "Hammerspoon | MacOSX | {{ current_spoon }} | Install {{ current_spoon }}" + ansible.builtin.unarchive: + src: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/{{ current_spoon }}.spoon.zip" + dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons" + remote_src: true - name: "Hammerspoon | MacOSX | {{ current_spoon }} | Cleanup latest {{ current_spoon }} zip" ansible.builtin.file: - name: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/{{ current_spoon }}.spoon.zip" + path: "{{ ansible_facts['user_dir'] }}/.hammerspoon/Spoons/{{ current_spoon }}.spoon.zip" state: absent diff --git a/roles/jj/tasks/main.yml b/roles/jj/tasks/main.yml index bbc7926b..7fd13433 100644 --- a/roles/jj/tasks/main.yml +++ b/roles/jj/tasks/main.yml @@ -9,7 +9,7 @@ when: distribution_config.stat.exists - name: "JJ | Read user.email secret from 1Password" - when: op_installed | default(false) + when: op_authenticated | default(false) block: - name: "1Password | Get user.email for JJ" ansible.builtin.command: "op --account my.1password.com read '{{ op.git.user.email }}'" @@ -38,4 +38,4 @@ ansible.builtin.template: src: "config.toml.j2" dest: "{{ ansible_facts['user_dir'] }}/.config/jj/config.toml" - mode: "0644" \ No newline at end of file + mode: "0644" diff --git a/roles/ssh/tasks/main.yml b/roles/ssh/tasks/main.yml index 1eb7c69f..68382b67 100644 --- a/roles/ssh/tasks/main.yml +++ b/roles/ssh/tasks/main.yml @@ -17,13 +17,20 @@ - name: "SSH | Deploy SSH keys from 1Password" ansible.builtin.include_tasks: ssh_keys.yml with_items: "{{ op.ssh.github.techdufus }}" - when: op_installed | default(false) + when: op_authenticated | default(false) - name: "SSH | Warning - 1Password not installed" ansible.builtin.debug: msg: "SSH key deployment skipped - 1Password CLI is not installed. Install 1Password CLI to enable automatic SSH key deployment." when: not (op_installed | default(false)) +- name: "SSH | Warning - 1Password not authenticated" + ansible.builtin.debug: + msg: "SSH key deployment skipped - 1Password is installed but not authenticated. Unlock 1Password, then rerun dotfiles -t 1password,ssh." + when: + - op_installed | default(false) + - not (op_authenticated | default(false)) + # - name: Copy config # ansible.builtin.template: # dest: "{{ ansible_facts['user_dir'] }}/.ssh/config" diff --git a/roles/zsh/files/os/MacOSX/os_functions.zsh b/roles/zsh/files/os/MacOSX/os_functions.zsh index 6db17276..2f2b9a52 100644 --- a/roles/zsh/files/os/MacOSX/os_functions.zsh +++ b/roles/zsh/files/os/MacOSX/os_functions.zsh @@ -1,4 +1,6 @@ #!/usr/bin/env zsh alias update='brew update && brew upgrade && brew cleanup' -export SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock +if [[ -S ~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock ]]; then + export SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock +fi From efd72785bfff69dcde6b8a1747f211d8ab172dad Mon Sep 17 00:00:00 2001 From: TechDufus Date: Sun, 8 Mar 2026 20:25:27 -0500 Subject: [PATCH 02/14] feat(fonts): install BerkeleyMono from 1Password --- group_vars/all.yml | 13 +++ pre_tasks/detect_1password.yml | 9 +- roles/1password/tasks/MacOSX.yml | 9 +- roles/fonts/README.md | 119 ++++++++++---------- roles/fonts/tasks/Fedora.yml | 30 +---- roles/fonts/tasks/MacOSX.yml | 9 +- roles/fonts/tasks/Ubuntu.yml | 11 +- roles/fonts/tasks/private_berkeley_mono.yml | 94 ++++++++++++++++ 8 files changed, 196 insertions(+), 98 deletions(-) create mode 100644 roles/fonts/tasks/private_berkeley_mono.yml diff --git a/group_vars/all.yml b/group_vars/all.yml index b908604c..32271715 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -64,6 +64,19 @@ op: user: email: "op://Personal/GitHub/email" allowed_signers: "op://Personal/TechDufus SSH/allowed_signers" + fonts: + berkeley_mono: + vault: "Private" + item: "BerkeleyMono Fonts" + files: + - field: BerkeleyMonoNerdFont-Regular + filename: BerkeleyMonoNerdFont-Regular.otf + - field: BerkeleyMonoNerdFont-Italic + filename: BerkeleyMonoNerdFont-Italic.otf + - field: BerkeleyMonoNerdFont-Bold + filename: BerkeleyMonoNerdFont-Bold.otf + - field: BerkeleyMonoNerdFont-BoldItalic + filename: BerkeleyMonoNerdFont-BoldItalic.otf ssh: github: techdufus: diff --git a/pre_tasks/detect_1password.yml b/pre_tasks/detect_1password.yml index 50faaaf0..ed09c4b4 100644 --- a/pre_tasks/detect_1password.yml +++ b/pre_tasks/detect_1password.yml @@ -17,7 +17,14 @@ - name: Detect 1Password authentication state ansible.builtin.command: - cmd: op whoami + argv: + - op + - vault + - list + - --account + - my.1password.com + - --format + - json changed_when: false failed_when: false register: op_whoami diff --git a/roles/1password/tasks/MacOSX.yml b/roles/1password/tasks/MacOSX.yml index 5c31506d..0b592a98 100644 --- a/roles/1password/tasks/MacOSX.yml +++ b/roles/1password/tasks/MacOSX.yml @@ -20,7 +20,14 @@ - name: "1Password | MacOSX | Detect authentication state" ansible.builtin.command: - cmd: op whoami + argv: + - op + - vault + - list + - --account + - my.1password.com + - --format + - json changed_when: false failed_when: false register: op_whoami diff --git a/roles/fonts/README.md b/roles/fonts/README.md index 08f24eb6..9fc755d0 100644 --- a/roles/fonts/README.md +++ b/roles/fonts/README.md @@ -1,60 +1,59 @@ # Fonts -A lightweight Ansible role for installing Powerline-compatible fonts across macOS, Ubuntu, and Fedora systems. Essential for terminal themes, status bars, and developer-focused applications. +An Ansible role for installing your private BerkeleyMono Nerd Font files across macOS, Ubuntu, and Fedora systems. ## Overview -This role ensures Powerline fonts are installed system-wide, providing the special glyphs and icons required by tools like tmux, vim-airline, oh-my-zsh themes, and Starship prompt. It handles platform-specific package managers automatically and provides graceful fallback instructions when sudo access is unavailable. +This role downloads BerkeleyMono Nerd Font files from a 1Password item in your Private vault, installs them into the current user's font directory, and skips automatically when the managed files already exist. ## Supported Platforms -| Platform | Package Manager | Font Package | -|----------|----------------|--------------| -| macOS | Homebrew | `font-powerline-symbols` | -| Ubuntu | APT | `fonts-powerline` | -| Fedora | DNF | `powerline-fonts` | +| Platform | BerkeleyMono Install Path | +|----------|---------------------------| +| macOS | `~/Library/Fonts/` | +| Ubuntu | `~/.local/share/fonts/` | +| Fedora | `~/.local/share/fonts/` | ## What Gets Installed -### Powerline Fonts Collection -- Special glyphs for terminal UI elements -- Patched symbols for status bars and prompts -- Unicode characters for branch icons, arrows, and separators -- Compatible with popular terminal themes and frameworks +### BerkeleyMono Nerd Font +- Pulled from a 1Password item named `BerkeleyMono Fonts` +- Expected as four file attachment fields: + - `BerkeleyMonoNerdFont-Regular` -> `BerkeleyMonoNerdFont-Regular.otf` + - `BerkeleyMonoNerdFont-Italic` -> `BerkeleyMonoNerdFont-Italic.otf` + - `BerkeleyMonoNerdFont-Bold` -> `BerkeleyMonoNerdFont-Bold.otf` + - `BerkeleyMonoNerdFont-BoldItalic` -> `BerkeleyMonoNerdFont-BoldItalic.otf` +- Installed per-user so the role stays idempotent and does not need sudo ### Installation Locations ```mermaid graph LR A[fonts role] --> B{OS Detection} - B -->|macOS| C[Homebrew Fonts Tap] - B -->|Ubuntu| D[/usr/share/fonts/] - B -->|Fedora| E[/usr/share/fonts/] - - C --> F[~/Library/Fonts/] - D --> G[System Font Cache] - E --> G + B -->|macOS| C[~/Library/Fonts] + B -->|Ubuntu| D[~/.local/share/fonts] + B -->|Fedora| E[~/.local/share/fonts] ``` ## Features ### Cross-Platform Consistency -- Automatic OS detection and appropriate package manager selection -- Idempotent installation (safe to run multiple times) +- Automatic OS detection and appropriate per-user install path selection +- Idempotent installation based on the managed BerkeleyMono font files - No configuration files or symlinks needed +### 1Password-Backed Private Fonts +- Uses `op read --out-file` to install the BerkeleyMono attachments directly from 1Password +- Skips cleanly when 1Password is not installed, not authenticated, or the item is missing +- Safe to rerun after authenticating with 1Password on a fresh machine + ### Graceful Degradation -On Fedora systems without sudo access, provides helpful manual installation instructions: -```bash -git clone https://github.com/powerline/fonts.git ~/.local/share/fonts/powerline -fc-cache -f ~/.local/share/fonts -``` +- Skips cleanly when 1Password is unavailable or unauthenticated +- Prints the exact rerun command once 1Password access is ready ### Clean Uninstallation The included `uninstall.sh` script removes: - Nerd Fonts from user directories -- Homebrew cask installations (macOS) -- System font directories (Linux) - Updates font cache automatically ## Usage @@ -74,10 +73,10 @@ dotfiles -t fonts --check ### Verify Installation ```bash # Check installed fonts (macOS) -ls ~/Library/Fonts/ | grep -i powerline +ls ~/Library/Fonts/ | grep -i BerkeleyMono # Check installed fonts (Linux) -fc-list | grep -i powerline +fc-list | grep -i BerkeleyMono # Test glyphs in terminal echo " \ue0b0 \ue0b1 \ue0b2 \ue0b3" @@ -86,21 +85,17 @@ echo " \ue0b0 \ue0b1 \ue0b2 \ue0b3" ## Dependencies ### Runtime Requirements -- **macOS**: Homebrew -- **Ubuntu**: APT package manager, sudo access -- **Fedora**: DNF package manager, sudo access (or manual installation) +- **Private BerkeleyMono install**: 1Password CLI authenticated with access to the `BerkeleyMono Fonts` item in the `Private` vault -### No Role Dependencies -This role is standalone and does not depend on other roles in the dotfiles repository. +### Role Dependencies +- Private BerkeleyMono installation depends on the `1password` role being installed and authenticated ## Integration These fonts are used by several other roles in this dotfiles collection: -- **tmux**: Status bar icons and separators -- **starship**: Git branch symbols and prompt decorations -- **neovim**: vim-airline and status line plugins -- **zsh**: oh-my-zsh themes with special characters +- **ghostty**: Uses `BerkeleyMono Nerd Font` +- **kitty**: Uses `BerkeleyMono Nerd Font` ## Technical Details @@ -109,39 +104,41 @@ These fonts are used by several other roles in this dotfiles collection: roles/fonts/ ├── tasks/ │ ├── main.yml # OS detection entry point -│ ├── MacOSX.yml # Homebrew installation -│ ├── Ubuntu.yml # APT installation -│ └── Fedora.yml # DNF installation with fallback +│ ├── MacOSX.yml # macOS user font install +│ ├── Ubuntu.yml # Linux user font install +│ ├── Fedora.yml # Linux user font install +│ └── private_berkeley_mono.yml └── uninstall.sh # Clean removal script ``` -### Package Selection Strategy -Each OS uses its native font packaging: -- **macOS**: Taps into `homebrew/cask-fonts` repository -- **Ubuntu**: Uses official Debian `fonts-powerline` package -- **Fedora**: Uses EPEL/RPM Fusion `powerline-fonts` package +### Install Strategy +Each OS installs BerkeleyMono as a user font from 1Password attachments: +- **macOS**: `~/Library/Fonts` +- **Ubuntu**: `~/.local/share/fonts` +- **Fedora**: `~/.local/share/fonts` ## Resources -- [Powerline Documentation](https://powerline.readthedocs.io/) -- [Powerline Fonts Repository](https://github.com/powerline/fonts) -- [Nerd Fonts](https://www.nerdfonts.com/) - Extended font collection with even more glyphs +- [Nerd Fonts](https://www.nerdfonts.com/) - Extended font collection with patched developer fonts ## Troubleshooting ### Fonts not appearing in terminal 1. Restart your terminal application after installation -2. Configure your terminal to use a Powerline-compatible font -3. Verify installation: `fc-list | grep -i powerline` (Linux) or `ls ~/Library/Fonts/` (macOS) +2. Configure your terminal to use `BerkeleyMono Nerd Font` +3. Verify installation: `fc-list | grep -i BerkeleyMono` (Linux) or `ls ~/Library/Fonts/ | grep -i BerkeleyMono` (macOS) + +### BerkeleyMono files were skipped +- Unlock or sign in to 1Password +- Confirm the `Private` vault contains an item named `BerkeleyMono Fonts` +- Confirm the item has file attachment fields named exactly: + - `BerkeleyMonoNerdFont-Regular` + - `BerkeleyMonoNerdFont-Italic` + - `BerkeleyMonoNerdFont-Bold` + - `BerkeleyMonoNerdFont-BoldItalic` +- Rerun `dotfiles -t fonts` ### Glyphs showing as boxes or question marks - Ensure your terminal emulator supports Unicode -- Check that the selected font is actually a Powerline-patched variant -- Try setting terminal font to "Monaco for Powerline" or similar - -### Manual installation needed (Fedora without sudo) -Follow the instructions displayed during role execution, or run: -```bash -git clone https://github.com/powerline/fonts.git ~/.local/share/fonts/powerline -fc-cache -f ~/.local/share/fonts -``` +- Check that the selected font is actually `BerkeleyMono Nerd Font` +- Reload the terminal after the font files land in the user font directory diff --git a/roles/fonts/tasks/Fedora.yml b/roles/fonts/tasks/Fedora.yml index b49150b7..007ee9f2 100644 --- a/roles/fonts/tasks/Fedora.yml +++ b/roles/fonts/tasks/Fedora.yml @@ -1,26 +1,6 @@ --- -- name: "Fonts | Install" - ansible.builtin.dnf: - name: - - powerline-fonts - state: present - become: true - when: can_install_packages | default(false) - -- name: "Fonts | Manual installation message" - ansible.builtin.debug: - msg: - - "⚠️ Powerline fonts cannot be installed without sudo access." - - - "" - - - "To install fonts manually:" - - - "1. Clone the powerline fonts repository:" - - - "git clone https://github.com/powerline/fonts.git ~/.local/share/fonts/powerline" - - - "2. Run fc-cache to update font cache:" - - - "fc-cache -f ~/.local/share/fonts" - when: not (can_install_packages | default(false)) \ No newline at end of file +- name: "Fonts | Fedora | Install BerkeleyMono Nerd Font" + ansible.builtin.include_tasks: private_berkeley_mono.yml + vars: + berkeley_mono_install_dir: "{{ ansible_env.HOME }}/.local/share/fonts" + berkeley_mono_refresh_cache: true diff --git a/roles/fonts/tasks/MacOSX.yml b/roles/fonts/tasks/MacOSX.yml index be22e352..8356628e 100644 --- a/roles/fonts/tasks/MacOSX.yml +++ b/roles/fonts/tasks/MacOSX.yml @@ -1,5 +1,6 @@ --- -- name: "Fonts | MacOSX | Install" - community.general.homebrew: - name: font-powerline-symbols - state: present +- name: "Fonts | MacOSX | Install BerkeleyMono Nerd Font" + ansible.builtin.include_tasks: private_berkeley_mono.yml + vars: + berkeley_mono_install_dir: "{{ ansible_env.HOME }}/Library/Fonts" + berkeley_mono_refresh_cache: false diff --git a/roles/fonts/tasks/Ubuntu.yml b/roles/fonts/tasks/Ubuntu.yml index eb78ad54..9156f8c7 100644 --- a/roles/fonts/tasks/Ubuntu.yml +++ b/roles/fonts/tasks/Ubuntu.yml @@ -1,7 +1,6 @@ --- -- name: "Fonts | Install" - ansible.builtin.apt: - name: - - fonts-powerline - state: present - become: true +- name: "Fonts | Ubuntu | Install BerkeleyMono Nerd Font" + ansible.builtin.include_tasks: private_berkeley_mono.yml + vars: + berkeley_mono_install_dir: "{{ ansible_env.HOME }}/.local/share/fonts" + berkeley_mono_refresh_cache: true diff --git a/roles/fonts/tasks/private_berkeley_mono.yml b/roles/fonts/tasks/private_berkeley_mono.yml new file mode 100644 index 00000000..2c007e02 --- /dev/null +++ b/roles/fonts/tasks/private_berkeley_mono.yml @@ -0,0 +1,94 @@ +--- +- name: "Fonts | BerkeleyMono | Ensure install directory exists" + ansible.builtin.file: + path: "{{ berkeley_mono_install_dir }}" + state: directory + mode: "0755" + +- name: "Fonts | BerkeleyMono | Check installed font files" + ansible.builtin.stat: + path: "{{ berkeley_mono_install_dir }}/{{ item.filename }}" + loop: "{{ op.fonts.berkeley_mono.files }}" + register: berkeley_mono_font_stats + loop_control: + label: "{{ item.filename }}" + +- name: "Fonts | BerkeleyMono | Record missing font files" + ansible.builtin.set_fact: + berkeley_mono_missing_fonts: >- + {{ + berkeley_mono_font_stats.results + | selectattr('stat.exists', 'equalto', false) + | map(attribute='item') + | list + }} + +- name: "Fonts | BerkeleyMono | Verify 1Password item exists" + ansible.builtin.command: + argv: + - op + - item + - get + - "{{ op.fonts.berkeley_mono.item }}" + - --vault + - "{{ op.fonts.berkeley_mono.vault }}" + - --format + - json + changed_when: false + failed_when: false + register: berkeley_mono_item + when: + - berkeley_mono_missing_fonts | length > 0 + - op_authenticated | default(false) + +- name: "Fonts | BerkeleyMono | Install missing font files" + ansible.builtin.command: + argv: + - op + - read + - --account + - my.1password.com + - --file-mode + - "0644" + - --out-file + - "{{ berkeley_mono_install_dir }}/{{ item.filename }}" + - "op://{{ op.fonts.berkeley_mono.vault }}/{{ op.fonts.berkeley_mono.item }}/{{ item.field }}" + loop: "{{ berkeley_mono_missing_fonts }}" + register: berkeley_mono_downloads + loop_control: + label: "{{ item.filename }}" + when: + - berkeley_mono_missing_fonts | length > 0 + - op_authenticated | default(false) + - berkeley_mono_item.rc == 0 + +- name: "Fonts | BerkeleyMono | Explain how to finish install without 1Password auth" + ansible.builtin.debug: + msg: + - "BerkeleyMono Nerd Font is not fully installed yet." + - "Authenticate 1Password, then rerun: dotfiles -t fonts" + when: + - berkeley_mono_missing_fonts | length > 0 + - not (op_authenticated | default(false)) + +- name: "Fonts | BerkeleyMono | Explain missing 1Password item" + ansible.builtin.debug: + msg: + - "1Password item '{{ op.fonts.berkeley_mono.item }}' was not found in vault '{{ op.fonts.berkeley_mono.vault }}'." + - "Create it with the four BerkeleyMono font attachments, then rerun: dotfiles -t fonts" + when: + - berkeley_mono_missing_fonts | length > 0 + - op_authenticated | default(false) + - berkeley_mono_item.rc != 0 + +- name: "Fonts | BerkeleyMono | Refresh font cache" + ansible.builtin.command: + argv: + - fc-cache + - -f + - "{{ berkeley_mono_install_dir }}" + changed_when: true + when: + - berkeley_mono_refresh_cache | default(false) + - berkeley_mono_downloads is defined + - berkeley_mono_downloads is changed From d310b479323c2479e191b1cd0491c11b4bd4015f Mon Sep 17 00:00:00 2001 From: TechDufus Date: Sun, 8 Mar 2026 20:28:30 -0500 Subject: [PATCH 03/14] fix(fonts): harden BerkeleyMono provisioning --- roles/1password/tasks/MacOSX.yml | 2 +- roles/fonts/tasks/private_berkeley_mono.yml | 2 ++ roles/fonts/uninstall.sh | 34 ++++++--------------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/roles/1password/tasks/MacOSX.yml b/roles/1password/tasks/MacOSX.yml index 0b592a98..d08de8bb 100644 --- a/roles/1password/tasks/MacOSX.yml +++ b/roles/1password/tasks/MacOSX.yml @@ -42,7 +42,7 @@ ansible.builtin.debug: msg: - "1Password is installed, but the CLI is not authenticated yet." - - "Open 1Password, sign in or unlock it, then rerun: dotfiles -t 1password,ssh,git,jj" + - "Open 1Password, sign in or unlock it, then rerun: dotfiles -t 1password,ssh,git,jj,fonts" when: - op_installed - not (op_authenticated | default(false)) diff --git a/roles/fonts/tasks/private_berkeley_mono.yml b/roles/fonts/tasks/private_berkeley_mono.yml index 2c007e02..83548195 100644 --- a/roles/fonts/tasks/private_berkeley_mono.yml +++ b/roles/fonts/tasks/private_berkeley_mono.yml @@ -30,6 +30,8 @@ - item - get - "{{ op.fonts.berkeley_mono.item }}" + - --account + - my.1password.com - --vault - "{{ op.fonts.berkeley_mono.vault }}" - --format diff --git a/roles/fonts/uninstall.sh b/roles/fonts/uninstall.sh index 531b3704..9a037414 100755 --- a/roles/fonts/uninstall.sh +++ b/roles/fonts/uninstall.sh @@ -1,8 +1,8 @@ #!/bin/bash set -e -echo -e "${YELLOW} [!] ${WHITE}This will remove Nerd Fonts installed by this role${NC}" -read -p "$(echo -e ${YELLOW})Remove Nerd Fonts? (y/N) ${NC}" -n 1 -r +echo -e "${YELLOW} [!] ${WHITE}This will remove BerkeleyMono Nerd Font files installed by this role${NC}" +read -p "$(echo -e ${YELLOW})Remove BerkeleyMono Nerd Font files? (y/N) ${NC}" -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo -e "${YELLOW} [!] ${WHITE}Font removal cancelled${NC}" @@ -13,34 +13,18 @@ fi case "$(uname -s)" in Darwin) # Remove from user font directory - if ls ~/Library/Fonts/*Nerd*.ttf >/dev/null 2>&1 || ls ~/Library/Fonts/*Nerd*.otf >/dev/null 2>&1; then - __task "Removing Nerd Fonts from user directory" - _cmd "rm -f ~/Library/Fonts/*Nerd*.ttf ~/Library/Fonts/*Nerd*.otf" + if ls ~/Library/Fonts/BerkeleyMonoNerdFont-*.otf >/dev/null 2>&1; then + __task "Removing BerkeleyMono Nerd Font files from user directory" + _cmd "rm -f ~/Library/Fonts/BerkeleyMonoNerdFont-*.otf" _task_done fi - - # Remove via Homebrew - if command -v brew >/dev/null 2>&1; then - for font in $(brew list --cask | grep font-.*-nerd-font); do - __task "Removing $font via Homebrew" - _cmd "brew uninstall --cask $font" - _task_done - done - fi ;; Linux) # Remove from user font directory - if ls ~/.local/share/fonts/*Nerd*.ttf >/dev/null 2>&1 || ls ~/.local/share/fonts/*Nerd*.otf >/dev/null 2>&1; then - __task "Removing Nerd Fonts from user directory" - _cmd "rm -f ~/.local/share/fonts/*Nerd*.ttf ~/.local/share/fonts/*Nerd*.otf" - _task_done - fi - - # Remove from system directory if installed there - if ls /usr/share/fonts/*Nerd*.ttf >/dev/null 2>&1 || ls /usr/share/fonts/*Nerd*.otf >/dev/null 2>&1; then - __task "Removing Nerd Fonts from system directory" - _cmd "sudo rm -f /usr/share/fonts/*Nerd*.ttf /usr/share/fonts/*Nerd*.otf" + if ls ~/.local/share/fonts/BerkeleyMonoNerdFont-*.otf >/dev/null 2>&1; then + __task "Removing BerkeleyMono Nerd Font files from user directory" + _cmd "rm -f ~/.local/share/fonts/BerkeleyMonoNerdFont-*.otf" _task_done fi @@ -53,4 +37,4 @@ case "$(uname -s)" in ;; esac -echo -e "${GREEN} [✓] ${WHITE}Nerd Fonts have been uninstalled${NC}" \ No newline at end of file +echo -e "${GREEN} [✓] ${WHITE}BerkeleyMono Nerd Font files have been uninstalled${NC}" From f2346cd7281916e47f23b251743c958b064b5414 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Sun, 8 Mar 2026 20:34:19 -0500 Subject: [PATCH 04/14] fix(ansible): replace deprecated env facts --- roles/fonts/tasks/Fedora.yml | 2 +- roles/fonts/tasks/MacOSX.yml | 2 +- roles/fonts/tasks/Ubuntu.yml | 2 +- roles/npm/templates/npmrc.j2 | 4 ++-- roles/system/templates/user-sudo.j2 | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/roles/fonts/tasks/Fedora.yml b/roles/fonts/tasks/Fedora.yml index 007ee9f2..72774801 100644 --- a/roles/fonts/tasks/Fedora.yml +++ b/roles/fonts/tasks/Fedora.yml @@ -2,5 +2,5 @@ - name: "Fonts | Fedora | Install BerkeleyMono Nerd Font" ansible.builtin.include_tasks: private_berkeley_mono.yml vars: - berkeley_mono_install_dir: "{{ ansible_env.HOME }}/.local/share/fonts" + berkeley_mono_install_dir: "{{ ansible_facts['user_dir'] }}/.local/share/fonts" berkeley_mono_refresh_cache: true diff --git a/roles/fonts/tasks/MacOSX.yml b/roles/fonts/tasks/MacOSX.yml index 8356628e..5bf3e3a0 100644 --- a/roles/fonts/tasks/MacOSX.yml +++ b/roles/fonts/tasks/MacOSX.yml @@ -2,5 +2,5 @@ - name: "Fonts | MacOSX | Install BerkeleyMono Nerd Font" ansible.builtin.include_tasks: private_berkeley_mono.yml vars: - berkeley_mono_install_dir: "{{ ansible_env.HOME }}/Library/Fonts" + berkeley_mono_install_dir: "{{ ansible_facts['user_dir'] }}/Library/Fonts" berkeley_mono_refresh_cache: false diff --git a/roles/fonts/tasks/Ubuntu.yml b/roles/fonts/tasks/Ubuntu.yml index 9156f8c7..82655e2f 100644 --- a/roles/fonts/tasks/Ubuntu.yml +++ b/roles/fonts/tasks/Ubuntu.yml @@ -2,5 +2,5 @@ - name: "Fonts | Ubuntu | Install BerkeleyMono Nerd Font" ansible.builtin.include_tasks: private_berkeley_mono.yml vars: - berkeley_mono_install_dir: "{{ ansible_env.HOME }}/.local/share/fonts" + berkeley_mono_install_dir: "{{ ansible_facts['user_dir'] }}/.local/share/fonts" berkeley_mono_refresh_cache: true diff --git a/roles/npm/templates/npmrc.j2 b/roles/npm/templates/npmrc.j2 index 6c80b25a..8e9a150f 100644 --- a/roles/npm/templates/npmrc.j2 +++ b/roles/npm/templates/npmrc.j2 @@ -1,10 +1,10 @@ # NPM Configuration {% if not can_install_packages | default(false) and not use_nvm %} -prefix={{ ansible_env.HOME }}/.npm-global +prefix={{ ansible_facts['user_dir'] }}/.npm-global {% endif %} init-author-name={{ git_user_name | default('') }} {% if npm_config is defined %} {% for key, value in npm_config.items() %} {{ key }}={{ value }} {% endfor %} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/roles/system/templates/user-sudo.j2 b/roles/system/templates/user-sudo.j2 index 17e4db91..8c8f5bae 100644 --- a/roles/system/templates/user-sudo.j2 +++ b/roles/system/templates/user-sudo.j2 @@ -1 +1 @@ -{{ ansible_env['USER'] }} ALL=(ALL) NOPASSWD: ALL +{{ ansible_facts['user_id'] }} ALL=(ALL) NOPASSWD: ALL From 5eddfae2f47a0c5e2d4ad80fe09675900a482b60 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Mon, 9 Mar 2026 12:49:59 -0500 Subject: [PATCH 05/14] chore(agents): trim repo guidance and codex defaults --- AGENTS.md | 198 ++-------------------------------- bin/AGENTS.md | 33 ------ docs/AGENTS_OPTIMIZATION.md | 48 --------- pre_tasks/AGENTS.md | 29 ----- roles/1password/AGENTS.md | 47 -------- roles/awesomewm/AGENTS.md | 42 -------- roles/awesomewm/README.md | 3 +- roles/bat/AGENTS.md | 30 ------ roles/codex/files/config.toml | 6 +- roles/docker/AGENTS.md | 37 ------- roles/fzf/AGENTS.md | 45 -------- roles/ghostty/AGENTS.md | 34 ------ roles/ghostty/README.md | 2 +- roles/git/AGENTS.md | 45 -------- roles/helm/AGENTS.md | 35 ------ roles/k9s/AGENTS.md | 34 ------ roles/lazygit/AGENTS.md | 33 ------ roles/neovim/AGENTS.md | 46 -------- roles/sesh/AGENTS.md | 42 -------- roles/ssh/AGENTS.md | 37 ------- roles/starship/AGENTS.md | 47 -------- roles/system/AGENTS.md | 42 -------- roles/tmux/AGENTS.md | 44 -------- roles/yazi/AGENTS.md | 36 ------- roles/zoxide/AGENTS.md | 37 ------- roles/zsh/AGENTS.md | 42 -------- 26 files changed, 11 insertions(+), 1063 deletions(-) delete mode 100644 bin/AGENTS.md delete mode 100644 docs/AGENTS_OPTIMIZATION.md delete mode 100644 pre_tasks/AGENTS.md delete mode 100644 roles/1password/AGENTS.md delete mode 100644 roles/awesomewm/AGENTS.md delete mode 100644 roles/bat/AGENTS.md delete mode 100644 roles/docker/AGENTS.md delete mode 100644 roles/fzf/AGENTS.md delete mode 100644 roles/ghostty/AGENTS.md delete mode 100644 roles/git/AGENTS.md delete mode 100644 roles/helm/AGENTS.md delete mode 100644 roles/k9s/AGENTS.md delete mode 100644 roles/lazygit/AGENTS.md delete mode 100644 roles/neovim/AGENTS.md delete mode 100644 roles/sesh/AGENTS.md delete mode 100644 roles/ssh/AGENTS.md delete mode 100644 roles/starship/AGENTS.md delete mode 100644 roles/system/AGENTS.md delete mode 100644 roles/tmux/AGENTS.md delete mode 100644 roles/yazi/AGENTS.md delete mode 100644 roles/zoxide/AGENTS.md delete mode 100644 roles/zsh/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md index 6c56415d..ad8ea916 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,195 +1,9 @@ # AGENTS.md -> **Generated:** 2026-01-02 | **Commit:** 9adcaf33 | **Branch:** main +This file is intentionally minimal. Add lines only for repo-specific landmines that are not obvious from the codebase. -Ansible-based dotfiles for cross-platform dev environment (macOS, Ubuntu, Fedora, Arch). 75+ roles, 1Password secrets, idempotent. - -## WHERE TO LOOK - -| Task | Location | Notes | -|------|----------|-------| -| Add new tool | `roles//` | Copy OS detection from existing role | -| Configure role | `group_vars/all.yml` | Add to `default_roles` list | -| Add secret | 1Password vault | Reference via `op://Vault/Item/field` | -| OS-specific logic | `roles//tasks/.yml` | MacOSX, Ubuntu, Fedora, Archlinux | -| Shell integration | `roles/zsh/files/zsh/` | 30+ function modules | -| Pre-flight checks | `pre_tasks/` | WSL, sudo, 1Password detection | - -## COMMANDS - -```bash -dotfiles # Install/update all -dotfiles -t neovim,git # Specific roles only -dotfiles --check # Dry run -dotfiles -vvv # Debug -dotfiles --list-tags # List roles -dotfiles --uninstall # Remove (keep config) -dotfiles --delete # Remove completely -``` - -## STRUCTURE - -``` -.dotfiles/ -├── main.yml # Entry point - role orchestration -├── group_vars/all.yml # Variables: default_roles, op secrets, packages -├── pre_tasks/ # Detection: WSL, sudo, 1Password -├── roles/ # 75+ self-contained tool configs -│ └── / -│ ├── tasks/main.yml # OS detection entry point -│ ├── tasks/.yml # Platform-specific tasks -│ ├── files/ # Static configs (symlinked) -│ ├── templates/ # Jinja2 templates (.j2) -│ ├── defaults/ # Role variables -│ └── uninstall.sh # Clean removal -└── bin/dotfiles # Bootstrap script with spinners -``` - -## CONVENTIONS - -### OS Detection Pattern (MANDATORY for every role) -```yaml -- name: "{{ role_name }} | Checking for Distribution Config: {{ ansible_facts['distribution'] }}" - ansible.builtin.stat: - path: "{{ role_path }}/tasks/{{ ansible_facts['distribution'] }}.yml" - register: distribution_config - -- name: "{{ role_name }} | Run Tasks: {{ ansible_facts['distribution'] }}" - ansible.builtin.include_tasks: "{{ ansible_facts['distribution'] }}.yml" - when: distribution_config.stat.exists -``` - -### Task Naming -```yaml -- name: "{{ role_name }} | Install | Package dependencies" -- name: "{{ role_name }} | Configure | User settings" -- name: "{{ role_name }} | Symlink | Configuration files" -``` - -### YAML Standards -- 2-space indent -- FQCNs: `ansible.builtin.copy` not `copy` -- Booleans: `true`/`false` not `yes`/`no` -- `|` for literal, `>` for folded multi-line - -### Config Deployment -- **Static files**: `ansible.builtin.copy` from `files/` -- **Dynamic files**: `ansible.builtin.template` with `.j2` -- **Prefer symlinks**: Maintains version control link - -## 1PASSWORD INTEGRATION - -```yaml -# Reading secrets - always check auth first -- name: "git | Get user email from 1Password" - ansible.builtin.shell: | - op --account my.1password.com read 'op://Dotfiles/Github/email' - register: git_user_email_op - when: op_installed and op_authenticated - failed_when: false -``` - -**Vault references** in `group_vars/all.yml`: -```yaml -op: - git: - user: - email: "op://Personal/GitHub/email" - ssh: - github: - techdufus: - - name: id_ed25519 - vault_path: "op://Personal/TechDufus SSH" -``` - -## ANTI-PATTERNS - -| Forbidden | Why | Instead | -|-----------|-----|---------| -| Secrets in repo | Security | Use `op://` references | -| `yes`/`no` booleans | Deprecated | Use `true`/`false` | -| Short module names | Deprecated | Use FQCNs | -| Uninstall git/python | System deps | Never remove critical packages | -| Skip `failed_when: false` for optional ops | Breaks non-critical paths | Always handle gracefully | - -## GOTCHAS - -### ZSH Completions in tmux -Completions fail due to timing. Solution: Load AFTER zinit's cdreplay or use precmd hook. - -### 1Password Vault Migration -Old: `~/.ansible-vault/vault.secret` (deprecated). New: All secrets via `op://`. Never store secrets in repo. - -### Ubuntu 22+ pip -System-managed Python blocks direct pip. Use role-specific installation approach. - -### Homebrew on Linux -Not auto-added to PATH. Manual shell config required. - -### WSL Detection -Checks `/proc/version` for Microsoft. PowerShell ExecutionPolicy must be RemoteSigned. - -### Dual GPU Cursor -Kitty custom cursor invisible on dual GPU. Disable in config. - -## ERROR HANDLING - -```yaml -# Non-critical operations -- name: "role | Optional feature" - command: some-command - failed_when: false - changed_when: false - -# Complex with fallback -- block: - - name: "role | Try operation" - command: risky-command - rescue: - - name: "role | Fallback operation" - command: safe-command -``` - -## ADDING A NEW ROLE - -1. `mkdir -p roles//{tasks,files,defaults}` -2. Copy OS detection pattern to `tasks/main.yml` -3. Create `tasks/MacOSX.yml`, `tasks/Ubuntu.yml` etc. -4. Add configs to `files/`, templates to `templates/` -5. Create `uninstall.sh` following existing patterns -6. Add to `default_roles` in `group_vars/all.yml` -7. Test: `dotfiles -t ` on each OS - -## CI QUALITY GATES - -| Check | Trigger | -|-------|---------| -| ansible-lint | roles/**/*.yml | -| shellcheck | **/*.sh | -| yamllint | **/*.yml, **/*.yaml | -| markdownlint | **/*.md | -| link-checker | docs/**/*.md | - -Run locally: -```bash -ansible-playbook main.yml --syntax-check -dotfiles --check -``` - -## ROLE DEPENDENCIES - -| Role | Depends On | Notes | -|------|------------|-------| -| npm | nvm | nvm must run first | -| git | ssh, 1password | For signing keys | -| Any with secrets | 1password | Must be authenticated | - -## PACKAGE MANAGERS - -| OS | Manager | Notes | -|----|---------|-------| -| macOS | brew, brew cask | Primary | -| Ubuntu | apt, nala (preferred) | Falls back to apt | -| Fedora | dnf | | -| Arch | pacman | | -| Cross-platform | pip, npm, go, cargo | Language-specific | +- Secrets are referenced via 1Password `op://...`; never add plaintext secrets to the repo. +- Optional host/integration work should fail soft instead of breaking the whole play, especially around sudo and 1Password. +- Secret-bearing tasks should use `no_log: true`. +- Uninstall flows must not remove critical system dependencies such as `git` or system `python`. +- Prefer fixing confusing structure, docs, or automation over growing this file. If guidance is still needed, keep it short and scoped to the affected directory. diff --git a/bin/AGENTS.md b/bin/AGENTS.md deleted file mode 100644 index 8389c24a..00000000 --- a/bin/AGENTS.md +++ /dev/null @@ -1,33 +0,0 @@ -# bin/ Directory - -Bootstrap and runner scripts for dotfiles. - -## Main Script: dotfiles - -The `dotfiles` script is the main entry point that bootstraps prerequisites, manages the dotfiles repository, and executes ansible-playbook. It auto-detects the OS and installs Ansible, Python, and package managers as needed before running the playbook. - -## Flow -1. Parse early flags (`--help`, `--version`) before any setup -2. Detect OS via `/etc/os-release` and run OS-specific setup (install Ansible, Python, etc.) -3. Clone or update the dotfiles repository from GitHub -4. Parse remaining args (`--uninstall`, `--delete`, or pass-through to ansible) -5. Install Ansible Galaxy dependencies from `requirements/` -6. Execute `ansible-playbook main.yml` with remaining arguments - -## Key Functions -- `detect_os`: Returns OS identifier (ubuntu, arch, fedora, darwin) -- `*_setup` (ubuntu/arch/fedora/macos): OS-specific prerequisite installation -- `__task`: Starts a spinner with task description -- `_task_done`: Stops spinner and shows success checkmark -- `_cmd`: Executes commands with error handling and logging to `~/.dotfiles.log` -- `run_uninstall_script`: Exports colors/functions and runs role's `uninstall.sh` -- `update_ansible_galaxy`: Installs common and OS-specific Galaxy requirements - -## Visual Feedback -Uses a braille character spinner (`_spinner`) running in a background process. The spinner cycles through `chars` array while displaying the current task. Colors use the Catppuccin Mocha palette via ANSI escape codes. Cursor visibility is controlled via `tput civis/cnorm`. - -## Gotchas -- Requires `tput` (ncurses) - exits immediately if not found -- First successful run creates `~/.dotfiles_run` marker to suppress welcome message -- All command output is hidden; errors are logged to `~/.dotfiles.log` and displayed on failure -- Exports functions/colors to child uninstall scripts via `export -f` diff --git a/docs/AGENTS_OPTIMIZATION.md b/docs/AGENTS_OPTIMIZATION.md deleted file mode 100644 index 36ae8cd4..00000000 --- a/docs/AGENTS_OPTIMIZATION.md +++ /dev/null @@ -1,48 +0,0 @@ -# AGENTS.md Optimization Summary - -## Overview - -Comprehensive optimization of AGENTS.md following OpenCode best practices from . - -## Changes Applied - -### 1. OpenCode Configuration Enhancement -- Added `instructions` array to `roles/opencode/files/opencode.json` -- Automatic loading of QUICKSTART.md, TROUBLESHOOTING.md, EXAMPLES.md -- Modular documentation architecture - -### 2. File Removal -- Deleted `PRPs/features/dotfilesctl-tui.feature.md` (should not have been committed) -- Cleaned up temporary feature specification file - -## Benefits - -**Token Efficiency:** -- Core AGENTS.md remains focused on critical patterns -- External docs loaded automatically via opencode.json -- Modular updates without AGENTS.md churn - -**Information Architecture:** -- AGENTS.md: Core patterns, commands, critical gotchas -- QUICKSTART.md: Bootstrap and installation -- TROUBLESHOOTING.md: Error resolution -- EXAMPLES.md: Configuration patterns - -**Maintenance:** -- Update external docs independently -- No monolithic file management -- Clearer separation of concerns - -## Implementation - -Applied OpenCode best practices: -1. Information density prioritization -2. Lazy loading pattern -3. Modular structure -4. Content compression -5. External file integration - ---- - -**Optimization completed**: October 20, 2025 -**Methodology**: OpenCode rules documentation + repository analysis diff --git a/pre_tasks/AGENTS.md b/pre_tasks/AGENTS.md deleted file mode 100644 index e84b9140..00000000 --- a/pre_tasks/AGENTS.md +++ /dev/null @@ -1,29 +0,0 @@ -# pre_tasks/ Directory - -Detection tasks that run before any roles execute. All tasks tagged `always`. - -## Detection Sequence -1. **WSL Detection**: Sets `ansible_host_environment_is_wsl` (bool) -2. **PowerShell Policy** (WSL only): Configures RemoteSigned execution policy -3. **WSL Host User** (WSL only): Sets `wsl_host_user` from Windows username -4. **Host User**: Sets `host_user` from `$USER` environment variable -5. **Sudo Detection**: Sets privilege escalation facts and package manager -6. **1Password Detection**: Sets `op_installed` (bool) - -## Key Variables Set -| Variable | Purpose | -|----------|---------| -| `ansible_host_environment_is_wsl` | True if running in WSL environment | -| `host_user` | Current Unix username | -| `wsl_host_user` | Windows username (WSL only) | -| `has_sudo` | Whether privilege escalation is available | -| `sudo_method` | Method used: `sudo`, `doas`, or `none` | -| `detected_package_manager` | OS package manager: `brew`, `apt`, `dnf`, `pacman`, `yum` | -| `can_install_packages` | Whether package installation is possible | -| `op_installed` | Whether 1Password CLI is available | - -## Gotchas -- WSL detection checks `/proc/version` for "microsoft" string -- Sudo detection tests passwordless, cached credentials, then doas/pkexec fallbacks -- macOS with Homebrew sets `can_install_packages: true` without requiring sudo -- 1Password detection only checks if `op` binary exists, not authentication state diff --git a/roles/1password/AGENTS.md b/roles/1password/AGENTS.md deleted file mode 100644 index 46d3738a..00000000 --- a/roles/1password/AGENTS.md +++ /dev/null @@ -1,47 +0,0 @@ -# 1Password Role - -Installs 1Password CLI and configures SSH agent for centralized secret management across the dotfiles system. - -## Key Files - -- `~/.config/1Password/ssh/agent.toml` - SSH agent vault configuration -- `files/agent.toml` - Source agent config -- `tasks/MacOSX.yml` - Homebrew cask install -- `tasks/Ubuntu.yml` - APT with GPG verification - -## Patterns - -- **op:// URL Format**: Secrets referenced as `op://vault/item/field` in `group_vars/all.yml` -- **Graceful Degradation**: All roles check `op_installed` fact before 1Password operations -- **no_log Required**: Always use `no_log: true` for secret retrieval tasks -- **failed_when: false**: Optional secrets should not fail the playbook - -## Integration - -- **Used by**: git role for user.email and allowed_signers -- **Used by**: ssh role for private/public key deployment -- **Used by**: zsh role for runtime secrets (API keys via `vars.secret_functions.zsh`) - -## Secret Reference Pattern - -```yaml -# In group_vars/all.yml -op: - git: - user: - email: "op://Personal/GitHub/email" - allowed_signers: "op://Personal/TechDufus SSH/allowed_signers" - ssh: - github: - techdufus: - - name: id_ed25519 - vault_path: "op://Personal/TechDufus SSH" -``` - -## Gotchas - -- **Role Disabled by Default**: Commented out in `group_vars/all.yml` - enable with `dotfiles -t 1password` -- **Authentication Required**: Must run `op signin my.1password.com` before roles that need secrets -- **Multi-Vault SSH**: `agent.toml` configures which vaults provide SSH keys (Personal, Raft, StarSage) -- **Ubuntu GPG Setup**: Uses dedicated keyring at `/usr/share/keyrings/1password-archive-keyring.gpg` -- **Test Agent**: Verify SSH keys with `ssh-add -l` after setup diff --git a/roles/awesomewm/AGENTS.md b/roles/awesomewm/AGENTS.md deleted file mode 100644 index d8b49d0f..00000000 --- a/roles/awesomewm/AGENTS.md +++ /dev/null @@ -1,42 +0,0 @@ -# AwesomeWM Role - -Installs AwesomeWM with Hammerspoon-inspired cell-based window management for Ubuntu. - -## Key Files -- `~/.config/awesome/rc.lua` - Main AwesomeWM config -- `~/.config/awesome/cell-management/apps.lua` - App registry with WM_CLASS and summon keys -- `~/.config/awesome/cell-management/positions.lua` - Named cell definitions (80x40 grid) -- `~/.config/awesome/cell-management/layouts.lua` - Layout definitions with app assignments -- `~/.config/awesome/cell-management/keybindings.lua` - Hyper key and F13 modal config - -## Patterns - -### 80x40 Virtual Grid -Resolution-independent positioning using `"x,y wxh"` format: -- `"0,0 52x40"` = 65% width, full height -- Grid maps to any screen resolution - -### F13 Modal Summoning -- Laptop: CapsLock remapped to F13 on startup -- External keyboard: Physical F13/F16 keys work directly -- Press F13, then letter key to summon app (e.g., F13+t = Ghostty) -- Same app twice = toggle back to previous app - -### WM_CLASS Matching -Apps identified by WM_CLASS (case-sensitive): -```bash -xprop WM_CLASS # Click window to find class -``` -Common values: `com.mitchellh.ghostty`, `brave-browser`, `Spotify` (capital S) - -## Integration -- **Mirrors**: Hammerspoon role (macOS) - same keybindings transfer -- **Hyper key**: Shift+Super+Alt+Ctrl for manual window management - -## Gotchas -- Ubuntu only (no macOS/Fedora/Arch support) -- CapsLock permanently remapped; use Shift for capitals -- WM_CLASS is case-sensitive (`Spotify` not `spotify`) -- No multi-monitor support in v1 -- No layout persistence across restarts -- Double-tap CapsLock (150ms) enters macro mode, not summon mode diff --git a/roles/awesomewm/README.md b/roles/awesomewm/README.md index 20ad53eb..7f6b2032 100644 --- a/roles/awesomewm/README.md +++ b/roles/awesomewm/README.md @@ -542,7 +542,6 @@ sequenceDiagram ### Internal Documentation -- **AGENTS.md:** [roles/awesomewm/AGENTS.md](AGENTS.md) - **Hammerspoon Role:** [roles/hammerspoon](../hammerspoon/) (macOS inspiration) ### External Resources @@ -560,7 +559,7 @@ When modifying this role: 1. Test on Ubuntu 22.04+ before committing 2. Verify configuration syntax: `awesome -k ~/.config/awesome/rc.lua` 3. Document new features in this README -4. Update AGENTS.md with implementation details +4. Document non-obvious landmines in the README only when they cannot be enforced in code 5. Follow [conventional commit](https://www.conventionalcommits.org/) format ## License diff --git a/roles/bat/AGENTS.md b/roles/bat/AGENTS.md deleted file mode 100644 index 35947345..00000000 --- a/roles/bat/AGENTS.md +++ /dev/null @@ -1,30 +0,0 @@ -# Bat Role - -Installs bat, a `cat` replacement with syntax highlighting and Git integration. - -## Key Files -- `~/.config/bat/config` - Main configuration -- `~/.config/bat/themes/Catppuccino Mocha.tmTheme` - Theme file (auto-downloaded) -- `files/config` - Source config in repo - -## Patterns -- **Theme auto-download**: Catppuccin theme fetched from GitHub during setup -- **Ubuntu .deb install**: Downloads latest release as .deb package from GitHub API -- **Version comparison**: Skips reinstall if current version matches latest - -## Key Config Settings -```bash ---theme="Catppuccino Mocha" ---style="numbers,changes,header" -``` - -## Integration -- **fzf**: Can be used as preview command in fzf -- **Git pager**: Configure with `git config --global core.pager "bat --paging=always"` -- **Man pages**: Use as MANPAGER with `col -bx | bat -l man -p` - -## Gotchas -- **Theme name has typo**: Config uses `Catppuccino Mocha` (note the "o") -- **Cache rebuild needed**: After adding themes run `bat cache --build` -- **aarch64 excluded on Ubuntu**: Installation skipped on ARM Ubuntu systems -- **No Arch support**: `Archlinux.yml` not implemented diff --git a/roles/codex/files/config.toml b/roles/codex/files/config.toml index cd94f64b..d4b0025d 100644 --- a/roles/codex/files/config.toml +++ b/roles/codex/files/config.toml @@ -3,12 +3,12 @@ # Minimal, high-quality defaults. model_reasoning_effort = "xhigh" -model_reasoning_summary = "detailed" -model_verbosity = "high" +# model_reasoning_summary = "detailed" +# model_verbosity = "high" approval_policy = "on-request" sandbox_mode = "danger-full-access" web_search = "live" -tool_output_token_limit = 120000 +# tool_output_token_limit = 120000 model = "gpt-5.4" plan_mode_reasoning_effort = "xhigh" service_tier = "fast" diff --git a/roles/docker/AGENTS.md b/roles/docker/AGENTS.md deleted file mode 100644 index 6dcffa9d..00000000 --- a/roles/docker/AGENTS.md +++ /dev/null @@ -1,37 +0,0 @@ -# Docker Role - -Installs Docker Engine with custom daemon configuration for network pools and user-space data storage. - -## Key Files -- `/etc/docker/daemon.json` - Custom daemon config (from `templates/daemon.json`) -- `~/.local/lib/docker` - Docker data directory (user-space, not system) -- `handlers/main.yml` - `restart_docker` handler for config changes - -## Patterns -- **Custom network pools**: Uses `172.18.0.0/16` range to avoid corporate/home network conflicts -- **User-space data-root**: Docker data stored in `~/.local/lib/docker` for easier backup -- **BuildKit enabled**: Experimental features and BuildKit enabled by default -- **Group management**: Adds user to `docker` group on Linux (requires logout/login) - -## Integration -- **podman**: Alternative runtime with Docker compatibility alias -- **Shell aliases**: `dprune` and `dsysprune` defined in `zsh/files/zsh/docker_aliases.zsh` -- **kind/whalebrew**: Other container-related roles depend on Docker - -## Daemon Configuration -```json -{ - "default-address-pools": [{"base": "172.18.0.0/16", "size": 24}], - "data-root": "~/.local/lib/docker", - "experimental": true, - "features": {"buildkit": true} -} -``` - -## Gotchas -- **macOS is CLI-only**: Homebrew installs CLI, not Docker Desktop (no daemon management) -- **WSL skips service management**: Service tasks have `when: not ansible_host_environment_is_wsl` -- **Group changes need re-login**: Docker group membership requires logout/login or `newgrp docker` -- **Sudo required on Linux**: Full Docker Engine install needs sudo; provides Podman fallback message -- **Optional role**: Commented out in `default_roles` - must be explicitly enabled -- **Data directory permissions**: `~/.local/lib/docker` created with mode 0710 diff --git a/roles/fzf/AGENTS.md b/roles/fzf/AGENTS.md deleted file mode 100644 index c3f5817a..00000000 --- a/roles/fzf/AGENTS.md +++ /dev/null @@ -1,45 +0,0 @@ -# FZF Role - -Installs fzf fuzzy finder with Catppuccin theming and shell integration for interactive filtering and selection. - -## Key Files - -- `~/.fzf/` - Source installation (Linux) -- `roles/zsh/files/zsh/fzf_config.zsh` - ZSH integration and theming -- `roles/zsh/files/zsh/git_functions.zsh` - FZF-powered git functions - -## Patterns - -- **Linux Source Install**: Removes apt/dnf package and installs from git for latest features -- **Handler-based Install**: Git clone triggers `~/.fzf/install --all --no-update-rc --no-fish` -- **Smart Preview**: Directories use `lsd --tree`, files use `bat` with syntax highlighting -- **Performance Limits**: Previews truncated (200 lines dirs, 500 lines files) to prevent lag - -## Integration - -- **Requires**: bat for syntax-highlighted file previews -- **Requires**: lsd for directory tree previews with icons -- **Used by**: zsh role for git functions (gco, glog, gstash, gws) -- **Used by**: tmux role for session switching (sesh + fzf) - -## FZF-Powered Functions - -- `gco` - Interactive branch checkout with commit preview -- `glog` - Commit history browser with diff preview -- `gstash` - Stash management (Enter=apply, Ctrl-P=pop, Ctrl-D=drop) -- `gws` - Worktree switching -- `kctx` - Kubernetes context switching - -## Default Keybindings - -- `Ctrl-T` - File/directory finder with preview -- `Ctrl-R` - Command history search -- `Alt-C` - Directory navigation - -## Gotchas - -- **macOS vs Linux Install**: Homebrew on macOS, git source on Linux (different paths) -- **Preview Dependencies**: Without bat/lsd, previews fall back to basic output -- **Catppuccin Theme**: Colors defined in `FZF_DEFAULT_OPTS` in fzf_config.zsh -- **Large Repos**: May need `.fzfignore` to exclude node_modules, vendor, .git -- **tmux Popups**: Require tmux 3.2+ for fzf popup windows diff --git a/roles/ghostty/AGENTS.md b/roles/ghostty/AGENTS.md deleted file mode 100644 index d02f896e..00000000 --- a/roles/ghostty/AGENTS.md +++ /dev/null @@ -1,34 +0,0 @@ -# Ghostty Role - -Configures Ghostty terminal emulator with GPU-accelerated effects and custom GLSL shaders. - -## Key Files -- `~/.config/ghostty/config` - Main configuration -- `~/.config/ghostty/shaders/` - Custom cursor effect shaders -- `files/shaders/cursor_blaze.glsl` - Electric trail effect -- `files/shaders/cursor_smear.glsl` - Smooth trailing effect (Catppuccin colors) - -## Patterns -- **Nightly builds required**: Uses `ghostty@tip` for background-image support -- **Custom shaders**: GLSL shaders for cursor effects using Ghostty's built-in variables -- **Hidden titlebar**: `macos-titlebar-style = hidden` for minimal UI - -## Key Config Settings -```ini -theme = catppuccin-mocha -font-family = "BerkeleyMono Nerd Font" -custom-shader = shaders/cursor_blaze.glsl -background-image-opacity = 0.5 -auto-update-channel = tip -``` - -## Integration -- **Catppuccin theme**: Consistent with dotfiles ecosystem theming -- **Shell integration**: `shell-integration-features = no-cursor` prevents shell cursor conflicts - -## Gotchas -- **macOS only**: Linux/Windows task files not implemented yet -- **Background images need nightly**: Stable releases lack `background-image` support -- **Shader GPU requirements**: Complex shaders may not work on older hardware -- **Legacy config path**: Uninstall handles both `~/.config/ghostty/` and `~/Library/Application Support/com.mitchellh.ghostty` -- **Font dependency**: Requires BerkeleyMono Nerd Font installed separately diff --git a/roles/ghostty/README.md b/roles/ghostty/README.md index 13ba7060..05c2d288 100644 --- a/roles/ghostty/README.md +++ b/roles/ghostty/README.md @@ -173,4 +173,4 @@ background-image-fit = cover # cover, contain, stretch, tile ## Advanced Configuration -For detailed customization options, shader development, and platform-specific notes, see [AGENTS.md](./AGENTS.md). +For detailed customization options, shader development, and platform-specific notes, use the upstream Ghostty docs plus this README. diff --git a/roles/git/AGENTS.md b/roles/git/AGENTS.md deleted file mode 100644 index 5740af51..00000000 --- a/roles/git/AGENTS.md +++ /dev/null @@ -1,45 +0,0 @@ -# Git Role - -Configures Git with SSH-based commit signing, 1Password credential integration, delta diff viewer, and cross-platform settings. - -## Key Files -- `~/.config/git/allowed_signers` - SSH keys for signature verification (from 1Password) -- `~/.config/git/commit_template` - 50/72 char commit message guide -- `files/commit_template` - Source template in repo - -## Patterns - -### 1Password for Secrets -All credentials fetched via 1Password CLI with graceful fallback: -```yaml -op: - git: - user: - email: "op://Personal/GitHub/email" - allowed_signers: "op://Personal/TechDufus SSH/allowed_signers" -``` - -### SSH Signing Over GPG -Uses SSH keys instead of GPG for simpler cross-platform signing: -- `user.signingkey = ~/.ssh/id_ed25519.pub` -- `gpg.format = ssh` -- Requires SSH role to run first for keys - -### Delta Diff Viewer -Enhanced diffs with `git-delta` - macOS only via Homebrew: -- Side-by-side diffs -- Syntax highlighting -- `merge.conflictStyle = zdiff3` - -## Integration -- **Requires**: SSH role (keys must exist before signing config) -- **Requires**: 1Password role (for credential retrieval) -- **Used by**: ZSH/Bash roles (provide `gacp`, `gss`, `glog` functions) -- **Used by**: GitHub CLI role (enhances PR workflows) - -## Gotchas -- Delta only installs on macOS; Linux uses standard diff -- `allowed_signers` file requires 1Password authentication -- Without 1Password auth, role succeeds but prints manual setup instructions -- Git aliases like `gbr` defined in role; shell functions in zsh/bash roles -- Never uninstalls git package (critical system dependency) diff --git a/roles/helm/AGENTS.md b/roles/helm/AGENTS.md deleted file mode 100644 index 7b85af8b..00000000 --- a/roles/helm/AGENTS.md +++ /dev/null @@ -1,35 +0,0 @@ -# Helm Role - -Installs Helm (Kubernetes package manager) and configures chart repositories from `group_vars/all.yml`. - -## Key Files -- `~/.config/helm/` - Helm config directory -- `~/.cache/helm/repository/` - Repository cache -- `group_vars/all.yml` - `helm.repos` list for chart repositories - -## Patterns -- **Repository management**: Uses `kubernetes.core.helm_repository` module to add repos from `helm.repos` variable -- **Ubuntu GPG verification**: APT installation uses keyring at `/usr/share/keyrings/helm.gpg` -- **Fedora fallback**: Primary DNF, fallback to GitHub release installer for user-local install - -## Integration -- **k9s**: Helm values plugin configured in k9s plugins.yaml (`Shift-V` to view values) -- **kubectl**: Required for Helm to communicate with clusters -- **Shell**: Completion loaded via shell configs; Starship shows Helm context - -## Repository Configuration Example -```yaml -# group_vars/all.yml -helm: - repos: - - name: traefik - url: https://helm.traefik.io/traefik - - name: prometheus-community - url: https://prometheus-community.github.io/helm-charts -``` - -## Gotchas -- **Repository config location**: Repos defined in `group_vars/all.yml` under `helm.repos`, not in role files -- **No Arch support**: `Archlinux.yml` not implemented -- **Requires kubectl context**: Helm operations fail without valid kubeconfig -- **APT key path matters**: Ubuntu uses `/usr/share/keyrings/helm.gpg` for secure key storage diff --git a/roles/k9s/AGENTS.md b/roles/k9s/AGENTS.md deleted file mode 100644 index c0332e53..00000000 --- a/roles/k9s/AGENTS.md +++ /dev/null @@ -1,34 +0,0 @@ -# K9s Role - -Installs k9s Kubernetes TUI with Catppuccin Mocha theme, custom aliases, and Helm values plugin. - -## Key Files -- `~/.config/k9s/config.yaml` - Main config (Linux) -- `~/Library/Application Support/k9s/config.yaml` - Main config (macOS) -- `~/.config/k9s/skins/catppuccin_mocha.yaml` - Theme file -- `~/.config/k9s/aliases.yaml` - Custom resource aliases (`:dep` for deployments) -- `~/.config/k9s/plugins.yaml` - Helm values plugin (press `v` on Helm releases) -- `files/` - Source configs in repo - -## Patterns - -### Catppuccin Theme -Transparent background with Catppuccin Mocha colors: -- Configured via `skin: catppuccin_mocha` in config.yaml -- Theme file in `skins/` subdirectory - -### Custom Aliases -Quick resource access: `:dep` navigates to deployments view. - -### Helm Plugin -Press `v` on Helm release to view values via `helm get values`. - -## Integration -- **Uses**: kubectl context (respects `KUBECONFIG` env var) -- **Uses**: Helm CLI for values plugin - -## Gotchas -- Config path differs: `~/.config/k9s/` (Linux) vs `~/Library/Application Support/k9s/` (macOS) -- Ubuntu/Fedora install from GitHub releases; macOS/Arch use package managers -- Mouse disabled by default (`enableMouse: false`) to allow terminal text selection -- Shell pod uses `killerAdmin` image with 100m CPU / 100Mi memory limits diff --git a/roles/lazygit/AGENTS.md b/roles/lazygit/AGENTS.md deleted file mode 100644 index f87a2394..00000000 --- a/roles/lazygit/AGENTS.md +++ /dev/null @@ -1,33 +0,0 @@ -# Lazygit Role - -Installs lazygit, a terminal UI for git commands, with minimal configuration approach. - -## Key Files -- `~/.config/lazygit/config.yml` - User config (not managed by role) -- `~/.config/lazygit/state.yml` - Auto-generated state tracking -- `handlers/main.yml` - Cleanup handlers for temp files - -## Patterns -- **Minimal config approach**: No opinionated settings; works with defaults -- **GitHub release install**: Ubuntu/Fedora use GitHub API for latest version -- **Version comparison**: Prevents unnecessary reinstalls on subsequent runs -- **User-local fallback**: Installs to `~/.local/bin/` when sudo unavailable - -## Integration -- **Neovim**: Two integration methods: - - `kdheepak/lazygit.nvim` plugin - - ToggleTerm with `_LAZYGIT_TOGGLE()` function -- **Keybinding**: `gg` opens lazygit in Neovim -- **Git role**: Inherits git config, SSH keys, GPG signing - -## Installation Paths -- **With sudo**: `/usr/local/bin/lazygit` -- **Without sudo**: `~/.local/bin/lazygit` -- **macOS**: Homebrew manages location - -## Gotchas -- **No config deployment**: Role installs binary only; user customizes post-install -- **Temp file cleanup**: Handlers remove `/tmp/lazygit*` after installation -- **Architecture detection**: Fedora uses `github_release` role with arch pattern matching -- **No checksum validation**: Downloads not verified (potential improvement) -- **State tracking**: `state.yml` auto-tracks recent repos and UI preferences diff --git a/roles/neovim/AGENTS.md b/roles/neovim/AGENTS.md deleted file mode 100644 index bf007fb5..00000000 --- a/roles/neovim/AGENTS.md +++ /dev/null @@ -1,46 +0,0 @@ -# Neovim Role - -Configures Neovim as a modern IDE with lazy.nvim plugin management, LSP via Mason, and Catppuccin theme. - -## Key Files -- `~/.config/nvim/` - Symlinked to `roles/neovim/files/` -- `files/init.lua` - Entry point loading techdufus config and lazy.nvim -- `files/lazy-lock.json` - Plugin version lockfile (commit this after updates) -- `files/lua/plugins/` - Individual plugin configs (lsp.lua, telescope.lua, etc.) -- `files/lua/techdufus/core/` - Options, keymaps, autocommands -- `files/lua/techdufus/init.lua` - ConfigMode setting ("rich" or "simple") - -## Patterns - -### lazy.nvim Plugin Management -Each plugin in `lua/plugins/` with lazy loading: -```lua -return { - "plugin/name", - event = "BufRead", -- Lazy load trigger - config = function() ... end, -} -``` -Use `:Lazy` to manage plugins, `:Lazy profile` for performance. - -### Mason LSP Management -Servers auto-installed via Mason (`lua/plugins/lsp.lua`): -- gopls, ansiblels, bashls, lua_ls, yamlls, etc. -- Use `:Mason` to manage servers - -### ConfigMode Setting -In `lua/techdufus/init.lua`: -- `"rich"` (default): Full icons, true color, transparent background -- `"simple"`: Basic 8-color for limited terminals - -## Integration -- **Uses**: vim-tmux-navigator for seamless pane navigation -- **Uses**: Gitsigns, Lazygit for Git workflows -- **Uses**: Copilot and Avante for AI assistance - -## Gotchas -- Space is leader key; arrow keys show warning (use hjkl) -- `lazy-lock.json` pins versions; run `:Lazy sync` then commit lockfile -- LSP keymaps: `gd` definition, `gr` references, `K` hover, `ca` actions -- Neo-tree replaces netrw; Oil available as alternative -- 2-space indentation, 90-char color column diff --git a/roles/sesh/AGENTS.md b/roles/sesh/AGENTS.md deleted file mode 100644 index e2a142d1..00000000 --- a/roles/sesh/AGENTS.md +++ /dev/null @@ -1,42 +0,0 @@ -# Sesh Role - -Configures sesh, a smart tmux session manager with zoxide and fzf integration. - -## Key Files -- `~/.config/sesh/sesh.toml` - Session configuration -- `files/sesh.toml` - Source config in repo -- Tmux keybinding in `roles/tmux/files/tmux/tmux.conf` - -## Patterns -- **Go package install**: Installed via `go` role from `group_vars/all.yml` -- **Default startup command**: Creates split layout with Claude in right pane -- **Predefined sessions**: `[[sessions]]` blocks for quick-access directories - -## Configuration Structure -```toml -[[sessions]] -name = "dotfiles" -path = "~/.dotfiles" - -[default_session] -startup_command = "tmux split-window -h -p 50 && ..." -``` - -## Integration -- **tmux**: `prefix + o` opens sesh popup (80% width, 70% height) -- **zoxide**: `Ctrl-x` in sesh switches to zoxide directory list -- **fzf**: Interactive selection with live session previews -- **fd**: `Ctrl-f` enables file finder mode - -## Tmux Popup Controls -- `Ctrl-a` - All sessions -- `Ctrl-t` - Tmux sessions only -- `Ctrl-g` - Config/Git directories -- `Ctrl-x` - Zoxide directories -- `Ctrl-d` - Kill selected session - -## Gotchas -- **Requires Go role**: Installed as Go package, not system package -- **Tmux config owns keybinding**: Sesh popup defined in tmux role, not here -- **Zoxide dependency**: `Ctrl-x` requires zoxide installed and populated -- **fd dependency**: File finder mode requires fd installed diff --git a/roles/ssh/AGENTS.md b/roles/ssh/AGENTS.md deleted file mode 100644 index 774a26ec..00000000 --- a/roles/ssh/AGENTS.md +++ /dev/null @@ -1,37 +0,0 @@ -# SSH Role - -Deploys SSH keys from 1Password vaults with proper permissions and security handling. - -## Key Files -- `~/.ssh/` - SSH directory (mode 0700) -- `~/.ssh/` - Private keys (mode 0600) -- `~/.ssh/.pub` - Public keys (mode 0644) -- `group_vars/all.yml` - `op.ssh` configuration for key paths - -## Patterns -- **1Password key retrieval**: Uses `op read` with `?ssh-format=openssh` for private keys -- **No logging**: All sensitive operations use `no_log: true` to prevent secrets in logs -- **Hierarchical config**: Keys organized as `op.ssh..` in group_vars - -## Configuration Structure -```yaml -# group_vars/all.yml -op: - ssh: - github: - techdufus: - - name: id_ed25519 - vault_path: "op://Personal/TechDufus SSH" -``` - -## Integration -- **1Password role**: Requires 1Password CLI installed and authenticated -- **Git role**: SSH keys used for Git authentication and commit signing -- **1Password SSH Agent**: macOS uses `~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock` - -## Gotchas -- **Requires 1Password auth**: Role skips silently if `op` not authenticated -- **SSH config commented out**: Template system ready but `config.j2` task disabled in main.yml -- **Key names matter**: `item.name` becomes both filename and `.pub` suffix -- **No config management yet**: Only deploys keys, not `~/.ssh/config` -- **WSL permissions**: May need special handling for Windows/Linux path translation diff --git a/roles/starship/AGENTS.md b/roles/starship/AGENTS.md deleted file mode 100644 index e45e97df..00000000 --- a/roles/starship/AGENTS.md +++ /dev/null @@ -1,47 +0,0 @@ -# Starship Role - -Installs Starship cross-shell prompt with Catppuccin theming, git status, and language-specific segments. - -## Key Files - -- `~/.config/starship.toml` - Prompt configuration -- `files/starship.toml` - Source config with full Catppuccin palettes - -## Patterns - -- **Catppuccin Latte Default**: Light theme palette, switch via `palette = "catppuccin_mocha"` for dark -- **Custom Segments**: `[custom.giturl]` detects GitHub/GitLab/Bitbucket, `[custom.docker]` shows when Dockerfile present -- **Performance Tuning**: Several modules disabled by default (gcloud, nodejs, memory_usage, time) -- **Fedora Multi-tier Install**: Tries DNF, falls back to installer script, then user directory - -## Integration - -- **Requires**: Nerd Font for icons to display correctly -- **Used with**: zsh role (alternative to Powerlevel10k - user choice) -- **Used with**: zsh-vi-mode for vimcmd_symbol display - -## Key Configuration Sections - -```toml -[character] -success_symbol = "[[󰄛](green) ❯](peach)" -error_symbol = "[[󰄛](red) ❯](peach)" - -[git_status] -format = '[\($all_status$ahead_behind\)]($style) ' -untracked = " " -modified = " " -staged = '[++\($count\)](green)' - -[kubernetes] -format = '[$symbol$context([\(](peach)$namespace[\)](peach))]($style) ' -disabled = false -``` - -## Gotchas - -- **Icons Not Showing**: Install a Nerd Font and configure terminal to use it -- **Slow Prompt**: Run `starship timings` to identify slow modules, disable as needed -- **Git Remote Detection**: Custom giturl segment requires git remote to be configured -- **Ubuntu Installer**: Uses `creates: /usr/local/bin/starship` for idempotency -- **Theme Switching**: Change `palette = "catppuccin_latte"` to mocha/macchiato/frappe diff --git a/roles/system/AGENTS.md b/roles/system/AGENTS.md deleted file mode 100644 index d50c1c83..00000000 --- a/roles/system/AGENTS.md +++ /dev/null @@ -1,42 +0,0 @@ -# System Role - -Installs essential system utilities (jq, iSCSI), configures passwordless sudo, and applies OS-specific performance optimizations. - -## Key Files -- `/etc/sudoers.d/{{ user }}` - Passwordless sudo config (Linux) -- `/private/etc/sudoers.d/{{ user }}` - Passwordless sudo config (macOS) -- `/etc/dnf/dnf.conf` - Fedora package manager optimizations -- `templates/user-sudo.j2` - Sudo template source - -## Patterns - -### Fedora Has Most Features -Fedora implementation is the most comprehensive: -- DNF parallel downloads (10), fastest mirror, delta RPMs -- Swappiness reduced to 10 -- ZRAM with zstd compression -- Automated weekly cleanup (kernels, journals, temp files) -- DNF automatic security updates - -Other platforms have minimal implementations. - -### WSL Clipboard Integration -Ubuntu detects WSL via `ansible_host_environment_is_wsl` and installs win32yank: -- Downloads to `/usr/local/bin/win32yank.exe` (with sudo) -- Falls back to `~/.local/bin/` without sudo -- Required for Neovim clipboard in WSL - -### Graceful Sudo Fallback -Uses `can_install_packages` variable to skip privileged operations: -- Package installs skipped with helpful message -- User-local alternatives used when possible - -## Integration -- **Foundation role**: Should run early; other roles depend on jq and sudo -- **Used by**: Neovim role (WSL clipboard via win32yank) - -## Gotchas -- Hosts management is DEPRECATED (archived in `hosts-management-archive.yml`) -- Previous hosts implementation overwrote entire `/etc/hosts` - dangerous -- Fedora validates sudoers with `visudo -cf`; other platforms do not -- Never uninstalls system packages like git or python diff --git a/roles/tmux/AGENTS.md b/roles/tmux/AGENTS.md deleted file mode 100644 index b99836be..00000000 --- a/roles/tmux/AGENTS.md +++ /dev/null @@ -1,44 +0,0 @@ -# Tmux Role - -Configures tmux terminal multiplexer with TPM plugins, vi-mode, and corporate environment support. - -## Key Files -- `~/.config/tmux/tmux.conf` - Main configuration -- `~/.tmux/plugins/tpm/` - Tmux Plugin Manager (auto-cloned) -- `files/tmux/tmux.conf` - Source config in repo - -## Patterns -- **TPM plugin management**: Plugins installed via `prefix + I` -- **Corporate TMPDIR**: ZSH sets `TMUX_TMPDIR="$HOME/tmp/tmux"` to avoid `/tmp` restrictions -- **Fedora AppImage fallback**: Downloads portable tmux when sudo unavailable - -## Key Plugins -- `tmux-sensible` - Sensible defaults -- `tmux-resurrect` + `tmux-continuum` - Session persistence -- `vim-tmux-navigator` - Seamless vim/tmux pane navigation -- `catppuccin/tmux` - Theme -- `tmux-fzf-url` - URL extraction - -## Key Bindings -- `prefix + o` - Sesh session manager popup -- `Alt+Shift+H/L` - Quick window switching -- `S` - Toggle pane synchronization -- `Ctrl+h/j/k/l` - Pane navigation (shared with Neovim) - -## Integration -- **Neovim**: `vim-tmux-navigator` requires matching Neovim plugin -- **Sesh**: Session manager popup via `prefix + o` -- **ZSH**: `TMUX_TMPDIR` set in `roles/zsh/files/zsh/vars.zsh` - -## Ghostty Integration -- `allow-passthrough on` enables OSC sequences (images, shell integration) -- `extended-keys on` + `extended-keys-format csi-u` for modern key handling -- `terminal-features 'xterm-ghostty:extkeys'` for Ghostty extended keys -- True color via `terminal-overrides ",xterm-ghostty:RGB"` - -## Gotchas -- **Windows start at 1**: Not 0, for easier keyboard access -- **1M line history**: Large scrollback buffer configured -- **Corporate /tmp issues**: Must use `TMUX_TMPDIR` in restricted environments -- **Plugin install required**: After first run, press `prefix + I` to install plugins -- **Both roles needed**: vim-tmux-navigator needs config in both tmux and neovim roles diff --git a/roles/yazi/AGENTS.md b/roles/yazi/AGENTS.md deleted file mode 100644 index 27381f2c..00000000 --- a/roles/yazi/AGENTS.md +++ /dev/null @@ -1,36 +0,0 @@ -# Yazi Role - -Terminal file manager with image preview support (via Kitty graphics protocol). - -## Key Files -- `~/.config/yazi/yazi.toml` - Core settings -- `~/.config/yazi/keymap.toml` - Keybindings -- `~/.config/yazi/theme.toml` - Catppuccin Mocha theme - -## Dependencies -Installed automatically: -- ffmpegthumbnailer - Video thumbnails -- unar - Archive extraction -- poppler - PDF previews -- fd/ripgrep - Fast search -- imagemagick - Image processing -- zoxide - Directory jumping (z command in yazi) - -## Key Bindings -- `j/k` - Navigate up/down -- `h/l` - Parent dir / Enter -- `Space` - Select file -- `y` - Yank (copy) -- `p` - Paste -- `d` - Trash -- `a` - Create file -- `r` - Rename -- `.` - Toggle hidden files -- `z` - Jump via zoxide -- `/` - Search -- `q` - Quit - -## Integration -- **tmux**: Requires `allow-passthrough on` for image previews (configured in tmux role) -- **Ghostty/Kitty**: Native image support via Kitty graphics protocol -- **zoxide**: `z` key jumps to frecent directories diff --git a/roles/zoxide/AGENTS.md b/roles/zoxide/AGENTS.md deleted file mode 100644 index 4f5f9335..00000000 --- a/roles/zoxide/AGENTS.md +++ /dev/null @@ -1,37 +0,0 @@ -# Zoxide Role - -Installs zoxide, a smarter `cd` command that learns directory navigation patterns via frecency. - -## Key Files -- `~/.local/share/zoxide/db.zo` - Binary database of directories -- Shell init in `roles/zsh/files/.zshrc` and `roles/bash/files/.bashrc` - -## Patterns -- **Frecency scoring**: Combines frequency and recency for directory ranking -- **No config files**: All customization via environment variables -- **zinit conflict resolution**: ZSH config runs `unalias zi` before zoxide init - -## Shell Integration -```bash -# ZSH (after unalias zi for zinit conflict) -eval "$(zoxide init zsh)" - -# Bash (via oh-my-bash plugin) -plugins=(zoxide) -``` - -## Integration -- **Sesh/tmux**: `Ctrl-x` in sesh popup shows zoxide directories -- **Neovim**: `telescope-zoxide` plugin for directory navigation -- **fzf-tab**: Enhanced completion for `__zoxide_z` command - -## Commands -- `z ` - Jump to matching directory -- `zi` - Interactive selection with fzf -- `z -` - Previous directory - -## Gotchas -- **Database needs seeding**: Visit directories first to populate database -- **zi alias conflict**: zinit also uses `zi`; zsh config handles this -- **Ubuntu 21.04+ only**: Native apt package not available on older versions -- **No Fedora support**: Task file not implemented diff --git a/roles/zsh/AGENTS.md b/roles/zsh/AGENTS.md deleted file mode 100644 index 67da3971..00000000 --- a/roles/zsh/AGENTS.md +++ /dev/null @@ -1,42 +0,0 @@ -# ZSH Role - -Installs ZSH shell with Zinit plugin manager, Powerlevel10k prompt, and 30+ modular configuration files for development tools. - -## Key Files - -- `~/.zshrc` - Main entry point -- `~/.p10k.zsh` - Powerlevel10k prompt config -- `~/.config/zsh/*.zsh` - Modular tool configs -- `files/.zshrc` - Source zshrc -- `files/zsh/` - All module files -- `files/os//os_functions.zsh` - OS-specific functions - -## Module Categories - -- **Core**: `vars.zsh` (Catppuccin colors), `paths_vars.zsh`, `dotfiles_completions.zsh` -- **Git**: `git_functions.zsh` (gss, gco, glog, worktrees) -- **Containers**: `docker_aliases.zsh`, `podman_aliases.zsh`, `k8s_functions.zsh` -- **Tools**: `neovim_functions.zsh`, `terraform_functions.zsh`, `claude_functions.zsh` -- **Integration**: `fzf_config.zsh`, `nvm_config.zsh`, `vars.secret_functions.zsh` - -## Patterns - -- **Plugin Loading Order**: P10k instant prompt first, then zinit plugins, then `zinit cdreplay -q` for completions -- **Module Auto-loading**: All `~/.config/zsh/*.zsh` files sourced automatically -- **Lazy NVM**: Node version manager can be lazy-loaded for faster startup -- **OS Functions**: Each distro has own `os_functions.zsh` with update aliases and SSH agent paths - -## Integration - -- **Requires**: fzf for interactive functions (gco, glog, gstash) -- **Requires**: bat, lsd for fzf previews -- **Requires**: 1password for `vars.secret_functions.zsh` (runtime secrets) -- **Used by**: Most roles depend on zsh being configured for shell integration - -## Gotchas - -- **tmux Completion Timing**: Completions can fail in tmux due to async compinit. Solution: `dotfiles_completions.zsh` uses precmd hook to defer registration until shell is ready -- **Zinit First Run**: Requires internet to clone plugins on first shell launch -- **P10k Configure**: Run `p10k configure` for wizard-based prompt customization -- **macOS SSH Agent**: Uses 1Password agent socket at `~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock` -- **Completion Cache**: Clear with `rm ~/.zcompdump* && exec zsh` if completions break From 4751b9df6ba5a5aea298ae6bdb4fcb911ef40dcd Mon Sep 17 00:00:00 2001 From: TechDufus Date: Tue, 10 Mar 2026 15:27:45 -0500 Subject: [PATCH 06/14] feat(codex): enable guardian approval and undo --- roles/codex/files/config.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roles/codex/files/config.toml b/roles/codex/files/config.toml index d4b0025d..0874acb4 100644 --- a/roles/codex/files/config.toml +++ b/roles/codex/files/config.toml @@ -14,7 +14,9 @@ plan_mode_reasoning_effort = "xhigh" service_tier = "fast" [features] +guardian_approval = true multi_agent = true +undo = true voice_transcription = true # # Improves compatibility on Linux hosts where Landlock is unavailable/restricted. # use_linux_sandbox_bwrap = true From 3b99d46aba8215667095bf886f7d631ea334a9dd Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 11 Mar 2026 10:30:20 -0500 Subject: [PATCH 07/14] feat(superwhisper): add macOS role and settings symlink --- group_vars/all.yml | 1 + roles/superwhisper/README.md | 25 +++++++++++ roles/superwhisper/defaults/main.yml | 5 +++ roles/superwhisper/files/settings.json | 35 +++++++++++++++ roles/superwhisper/tasks/MacOSX.yml | 61 ++++++++++++++++++++++++++ roles/superwhisper/tasks/main.yml | 9 ++++ 6 files changed, 136 insertions(+) create mode 100644 roles/superwhisper/README.md create mode 100644 roles/superwhisper/defaults/main.yml create mode 100644 roles/superwhisper/files/settings.json create mode 100644 roles/superwhisper/tasks/MacOSX.yml create mode 100644 roles/superwhisper/tasks/main.yml diff --git a/group_vars/all.yml b/group_vars/all.yml index 32271715..7693c582 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -45,6 +45,7 @@ default_roles: # - rust - slides - spotify + - superwhisper - ssh - sshfs - starship diff --git a/roles/superwhisper/README.md b/roles/superwhisper/README.md new file mode 100644 index 00000000..068b0b21 --- /dev/null +++ b/roles/superwhisper/README.md @@ -0,0 +1,25 @@ +# superwhisper + +Installs `superwhisper` on macOS and manages the portable settings file by symlink. + +## What this role does + +- Installs `superwhisper` via Homebrew cask +- Symlinks `~/Documents/Superwhisper/settings/settings.json` to the repo-managed file at `roles/superwhisper/files/settings.json` +- Backs up any existing non-symlinked `settings.json` to `settings.json.backup` before replacing it +- If `superwhisper.app` is already present in `/Applications`, the role skips the cask install instead of failing and still manages `settings.json` + +## What this role does not do + +- It does not manage `~/Library/Preferences/com.superduper.superwhisper.plist` +- It does not manage hotkeys or other macOS preference keys + +Those preferences are intentionally excluded because macOS preference writes do not reliably preserve symlinks in `~/Library/Preferences`. + +## Usage + +```bash +dotfiles -t superwhisper +``` + +After the role runs, edits made by superwhisper to `settings.json` will update the tracked file in this repo automatically via the symlink. diff --git a/roles/superwhisper/defaults/main.yml b/roles/superwhisper/defaults/main.yml new file mode 100644 index 00000000..dd85f9ee --- /dev/null +++ b/roles/superwhisper/defaults/main.yml @@ -0,0 +1,5 @@ +--- +role_name: superwhisper +superwhisper_settings_source: "{{ role_path }}/files/settings.json" +superwhisper_settings_dir: "{{ ansible_facts['env']['HOME'] }}/Documents/Superwhisper/settings" +superwhisper_settings_dest: "{{ superwhisper_settings_dir }}/settings.json" diff --git a/roles/superwhisper/files/settings.json b/roles/superwhisper/files/settings.json new file mode 100644 index 00000000..8be4e52b --- /dev/null +++ b/roles/superwhisper/files/settings.json @@ -0,0 +1,35 @@ +{ + "favoriteModelIDs": [], + "modeKeys": [ + "voice to text", + "super", + "default" + ], + "replacements": [ + { + "id": "A835762E-0B8E-4E89-9CCE-08B377560B97", + "original": "doofus", + "with": "dufus" + }, + { + "id": "7397E7A6-0BBA-49CF-835B-91036E174CAC", + "original": "clodcode", + "with": "Claude Code" + }, + { + "id": "05B1E9DE-80E1-4781-9D6D-3B43DB48430F", + "original": "clod", + "with": "claude" + }, + { + "id": "CAD18F92-83D5-4BAD-9D82-E74F9D580E6D", + "original": "open claw", + "with": "OpenClaw" + } + ], + "vocabulary": [ + "Claude Code", + "codex", + "oh-my-claude" + ] +} diff --git a/roles/superwhisper/tasks/MacOSX.yml b/roles/superwhisper/tasks/MacOSX.yml new file mode 100644 index 00000000..efe419cd --- /dev/null +++ b/roles/superwhisper/tasks/MacOSX.yml @@ -0,0 +1,61 @@ +--- +- name: "{{ role_name }} | MacOSX | Check if Homebrew already manages superwhisper" + ansible.builtin.command: + cmd: brew list --cask superwhisper + register: superwhisper_cask_installed + changed_when: false + failed_when: false + +- name: "{{ role_name }} | MacOSX | Check if superwhisper.app already exists" + ansible.builtin.stat: + path: /Applications/superwhisper.app + register: superwhisper_app_bundle + +- name: "{{ role_name }} | MacOSX | Install superwhisper" + community.general.homebrew_cask: + name: superwhisper + state: present + when: + - superwhisper_cask_installed.rc != 0 + - not superwhisper_app_bundle.stat.exists + +- name: "{{ role_name }} | MacOSX | Skip Homebrew install when manual app already exists" + ansible.builtin.debug: + msg: >- + superwhisper.app already exists in /Applications but is not managed by Homebrew. + Skipping cask install and continuing with settings management. + when: + - superwhisper_cask_installed.rc != 0 + - superwhisper_app_bundle.stat.exists + +- name: "{{ role_name }} | MacOSX | Ensure settings directory exists" + ansible.builtin.file: + path: "{{ superwhisper_settings_dir }}" + state: directory + mode: "0755" + +- name: "{{ role_name }} | MacOSX | Check if settings.json already exists" + ansible.builtin.stat: + path: "{{ superwhisper_settings_dest }}" + register: superwhisper_settings_file + +- name: "{{ role_name }} | MacOSX | Backup existing settings.json if it is not a symlink" + ansible.builtin.copy: + src: "{{ superwhisper_settings_dest }}" + dest: "{{ superwhisper_settings_dest }}.backup" + remote_src: true + mode: "0644" + when: superwhisper_settings_file.stat.exists and not superwhisper_settings_file.stat.islnk + +- name: "{{ role_name }} | MacOSX | Remove existing settings.json if it is not a symlink" + ansible.builtin.file: + path: "{{ superwhisper_settings_dest }}" + state: absent + when: superwhisper_settings_file.stat.exists and not superwhisper_settings_file.stat.islnk + +- name: "{{ role_name }} | MacOSX | Symlink settings.json" + ansible.builtin.file: + src: "{{ superwhisper_settings_source }}" + dest: "{{ superwhisper_settings_dest }}" + state: link + force: true diff --git a/roles/superwhisper/tasks/main.yml b/roles/superwhisper/tasks/main.yml new file mode 100644 index 00000000..24345a1f --- /dev/null +++ b/roles/superwhisper/tasks/main.yml @@ -0,0 +1,9 @@ +--- +- name: "{{ role_name }} | Checking for Distribution Config: {{ ansible_facts['distribution'] }}" + ansible.builtin.stat: + path: "{{ role_path }}/tasks/{{ ansible_facts['distribution'] }}.yml" + register: distribution_config + +- name: "{{ role_name }} | Run Tasks: {{ ansible_facts['distribution'] }}" + ansible.builtin.include_tasks: "{{ ansible_facts['distribution'] }}.yml" + when: distribution_config.stat.exists From 07c5803cc9873a931c1c476480268925e9dba199 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 11 Mar 2026 13:24:22 -0500 Subject: [PATCH 08/14] fix(bootstrap): stop installing 1password in shell script --- bin/dotfiles | 9 --------- 1 file changed, 9 deletions(-) diff --git a/bin/dotfiles b/bin/dotfiles index fdcdccc1..badc0142 100755 --- a/bin/dotfiles +++ b/bin/dotfiles @@ -361,15 +361,6 @@ macos_setup() { __task "Installing Ansible" _cmd "brew install ansible" fi - if ! [ -x "$(command -v op)" ]; then - __task "Installing 1Password CLI" - _cmd "brew install 1password@beta" - _cmd "brew install 1password-cli@beta" - __task "Setting up 1Password CLI" - # press any key to continue - read -n 1 -s -r -p "Press any key to continue" - _task_done - fi } update_ansible_galaxy() { From a47c6f0f20b1d9b3f19497b06532e798356bff80 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 11 Mar 2026 14:43:15 -0500 Subject: [PATCH 09/14] fix(hammerspoon): copy screenshots to clipboard --- roles/hammerspoon/README.md | 2 +- roles/hammerspoon/files/config/init.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/hammerspoon/README.md b/roles/hammerspoon/README.md index 7ba9bb25..55cca584 100644 --- a/roles/hammerspoon/README.md +++ b/roles/hammerspoon/README.md @@ -70,7 +70,7 @@ Quick application switching with intelligent toggle behavior: #### F16 Modal - Quick Macros | Key | Action | Shortcut | |-----|--------|----------| -| `s` | Screenshot | ⌘⇧4 | +| `s` | Screenshot to Clipboard | ⌘⌃⇧4 | | `e` | Emoji Picker | ⌘⌃Space | | `a` | Next Window | ⌘` | | `b` | Browser Bookmark Search | Hyper+b (Raycast) | diff --git a/roles/hammerspoon/files/config/init.lua b/roles/hammerspoon/files/config/init.lua index 718b89bc..7e89210d 100644 --- a/roles/hammerspoon/files/config/init.lua +++ b/roles/hammerspoon/files/config/init.lua @@ -37,7 +37,7 @@ require('karabiner').start() -- F16 to open macros modal (created first so summon modal can reference it) local macros = { - s = function() hs.eventtap.keyStroke({ 'cmd', 'shift' }, '4') end, -- screenshot + s = function() hs.eventtap.keyStroke({ 'cmd', 'ctrl', 'shift' }, '4') end, -- screenshot to clipboard e = function() hs.eventtap.keyStroke({ 'cmd', 'ctrl' }, 'space') end, -- emoji picker a = function() hs.eventtap.keyStroke({ 'cmd' }, '`') end, -- next window of focused app -- c = function() hs.eventtap.keyStroke({ 'cmd', 'ctrl' }, 'c') end, -- color picker app From 32c503c2efadafbdb68b35242f51b4864e712715 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 11 Mar 2026 15:12:55 -0500 Subject: [PATCH 10/14] fix(bun): install from official Homebrew tap --- roles/bun/README.md | 5 +++-- roles/bun/tasks/MacOSX.yml | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/roles/bun/README.md b/roles/bun/README.md index 9f71e54a..d839da8e 100644 --- a/roles/bun/README.md +++ b/roles/bun/README.md @@ -6,7 +6,7 @@ Ansible role for installing [Bun](https://bun.sh/) - a fast all-in-one JavaScrip | Platform | Installation Method | Status | |----------|-------------------|--------| -| macOS | Homebrew | Supported | +| macOS | Official Homebrew tap | Supported | | Ubuntu | Official install script | Supported | | Fedora | - | Not supported | | Arch | - | Not supported | @@ -15,7 +15,7 @@ Ansible role for installing [Bun](https://bun.sh/) - a fast all-in-one JavaScrip - **Bun runtime** - Fast JavaScript/TypeScript runtime, bundler, and package manager - **Ubuntu**: Installed to `~/.bun/bin/bun` via official script -- **macOS**: Installed via Homebrew +- **macOS**: Installed via the official `oven-sh/bun` Homebrew tap ## Configuration @@ -40,5 +40,6 @@ bun --version ## Notes - Ubuntu installation requires `unzip` (installed automatically if sudo available) +- macOS installation uses `brew tap oven-sh/bun` before installing Bun - Bun auto-upgrades on subsequent runs if already installed - PATH setup handled by shell role (zsh/bash) diff --git a/roles/bun/tasks/MacOSX.yml b/roles/bun/tasks/MacOSX.yml index 2c549d3b..2ffae7c2 100644 --- a/roles/bun/tasks/MacOSX.yml +++ b/roles/bun/tasks/MacOSX.yml @@ -1,5 +1,10 @@ --- +- name: "{{ role_name }} | MacOSX | Add official Bun Homebrew tap" + community.general.homebrew_tap: + name: oven-sh/bun + state: present + - name: "{{ role_name }} | MacOSX | Install bun via Homebrew" community.general.homebrew: - name: bun + name: oven-sh/bun/bun state: present From c713294b80653e4f6d616d1e8b6968fe650a44b8 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 11 Mar 2026 21:46:11 -0500 Subject: [PATCH 11/14] feat(codex): manage peon notifications --- .gitignore | 2 + roles/codex/defaults/main.yml | 10 ++ roles/codex/files/config.toml | 7 ++ roles/codex/files/notify-peon.sh | 143 +++++++++++++++++++++++++++++ roles/codex/files/peon-config.json | 20 ++++ roles/codex/tasks/MacOSX.yml | 5 + roles/codex/tasks/Ubuntu.yml | 57 ++++++++++++ roles/codex/tasks/main.yml | 107 +++++++++++++++++++++ 8 files changed, 351 insertions(+) create mode 100755 roles/codex/files/notify-peon.sh create mode 100644 roles/codex/files/peon-config.json diff --git a/.gitignore b/.gitignore index 0721cf7d..1729ad0c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ roles/claude/files/commands/raft/ roles/claude/files/skills/rafty roles/claude/files/commands/raft-*.md roles/claude/files/agents/raft-*.md +roles/claude/files/hooks/peon-ping/ +roles/claude/files/skills/peon-ping-*/ # Crush directory .crush/ diff --git a/roles/codex/defaults/main.yml b/roles/codex/defaults/main.yml index fc87e35d..4c3f5cff 100644 --- a/roles/codex/defaults/main.yml +++ b/roles/codex/defaults/main.yml @@ -5,8 +5,18 @@ codex_agents_source: "{{ role_path }}/files/AGENTS.md" codex_agents_dest: "{{ ansible_facts['env']['HOME'] }}/.codex/AGENTS.md" codex_config_source: "{{ role_path }}/files/config.toml" codex_config_dest: "{{ ansible_facts['env']['HOME'] }}/.codex/config.toml" +codex_notify_script_source: "{{ role_path }}/files/notify-peon.sh" +codex_notify_script_dest: "{{ ansible_facts['env']['HOME'] }}/.codex/notify-peon.sh" codex_skills_source: "{{ role_path }}/files/skills" codex_skills_dest: "{{ ansible_facts['env']['HOME'] }}/.codex/skills" codex_cleanup_legacy_official_skills: true codex_legacy_official_skills_prefix: "{{ ansible_facts['env']['HOME'] }}/.local/share/codex/skills/openai-skills/skills/.curated/" codex_legacy_official_skills_cache_dir: "{{ ansible_facts['env']['HOME'] }}/.local/share/codex/skills/openai-skills" +codex_peon_enabled: true +codex_peon_dir: "{{ ansible_facts['env']['HOME'] }}/.openpeon" +codex_peon_install_base: "{{ ansible_facts['env']['HOME'] }}/.local/share/codex-peon" +codex_peon_install_dir: "{{ codex_peon_install_base }}/hooks/peon-ping" +codex_peon_config_source: "{{ role_path }}/files/peon-config.json" +codex_peon_config_dest: "{{ codex_peon_dir }}/config.json" +codex_peon_default_pack: "jarvis-mk2" +codex_peon_install_script_url: "https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh" diff --git a/roles/codex/files/config.toml b/roles/codex/files/config.toml index 0874acb4..77a6ef9f 100644 --- a/roles/codex/files/config.toml +++ b/roles/codex/files/config.toml @@ -12,6 +12,12 @@ web_search = "live" model = "gpt-5.4" plan_mode_reasoning_effort = "xhigh" service_tier = "fast" +notify = [ + "sh", + "-c", + 'exec "$HOME/.codex/notify-peon.sh" "$1"', + "sh", +] [features] guardian_approval = true @@ -45,3 +51,4 @@ args = ["-y", "@playwright/mcp@latest"] environment = "dev" log_user_prompt = true trace_exporter = "none" + diff --git a/roles/codex/files/notify-peon.sh b/roles/codex/files/notify-peon.sh new file mode 100755 index 00000000..a096d98e --- /dev/null +++ b/roles/codex/files/notify-peon.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +set -euo pipefail + +resolve_peon_sh() { + local peon_bin peon_bin_real peon_prefix candidate + + candidate="${CODEX_PEON_INSTALL_DIR:-$HOME/.local/share/codex-peon/hooks/peon-ping}/peon.sh" + if [ -f "$candidate" ]; then + printf '%s\n' "$candidate" + return 0 + fi + + if command -v peon >/dev/null 2>&1; then + peon_bin="$(command -v peon)" + peon_bin_real="$(python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$peon_bin" 2>/dev/null || true)" + if [ -n "$peon_bin_real" ]; then + peon_prefix="$(cd "$(dirname "$peon_bin_real")/.." && pwd)" + candidate="$peon_prefix/libexec/peon.sh" + if [ -f "$candidate" ]; then + printf '%s\n' "$candidate" + return 0 + fi + fi + fi + + for candidate in \ + /opt/homebrew/opt/peon-ping/libexec/peon.sh \ + /usr/local/opt/peon-ping/libexec/peon.sh + do + if [ -f "$candidate" ]; then + printf '%s\n' "$candidate" + return 0 + fi + done + + return 1 +} + +PEON_SH="$(resolve_peon_sh || true)" +[ -n "$PEON_SH" ] || exit 0 + +PEON_DIR="${CODEX_PEON_DIR:-$HOME/.openpeon}" +CODEX_NOTIFY_PAYLOAD="${1:-}" +if [ -z "$CODEX_NOTIFY_PAYLOAD" ] && [ ! -t 0 ]; then + CODEX_NOTIFY_PAYLOAD="$(cat)" +fi +[ -n "$CODEX_NOTIFY_PAYLOAD" ] || exit 0 + +CODEX_NOTIFY_PAYLOAD="$CODEX_NOTIFY_PAYLOAD" python3 - <<'PY' | CLAUDE_PEON_DIR="$PEON_DIR" bash "$PEON_SH" +import json +import os +import re + + +def first_non_empty(*values): + for value in values: + if value is None: + continue + if isinstance(value, str): + if value.strip(): + return value.strip() + else: + return value + return "" + + +raw = os.environ.get("CODEX_NOTIFY_PAYLOAD", "").strip() +payload_in = {} +if raw: + try: + parsed = json.loads(raw) + if isinstance(parsed, dict): + payload_in = parsed + except Exception: + payload_in = {} + +raw_event = first_non_empty( + payload_in.get("type", ""), + payload_in.get("event", ""), + "agent-turn-complete", +) +event_key = str(raw_event).strip().lower().replace("_", "-") + +notif_type = str(payload_in.get("notification_type", "")).strip().lower() +if ( + event_key.startswith("permission") + or event_key.startswith("approve") + or event_key in ("approval-requested", "approval-needed", "input-required") + or notif_type == "permission_prompt" +): + mapped_event = "Notification" + mapped_ntype = "permission_prompt" +elif event_key in ("start", "session-start"): + mapped_event = "SessionStart" + mapped_ntype = notif_type +elif event_key == "idle-prompt": + mapped_event = "Notification" + mapped_ntype = "idle_prompt" +elif event_key.startswith("error") or event_key.startswith("fail"): + mapped_event = "PostToolUseFailure" + mapped_ntype = notif_type +else: + mapped_event = "Stop" + mapped_ntype = notif_type + +cwd = str(first_non_empty(payload_in.get("cwd", ""), os.environ.get("PWD", ""), "/")) +raw_session_id = str( + first_non_empty( + payload_in.get("thread-id", ""), + payload_in.get("thread_id", ""), + payload_in.get("session_id", ""), + payload_in.get("conversation_id", ""), + os.getpid(), + ) +) +safe_session_id = re.sub(r"[^A-Za-z0-9._:-]", "-", raw_session_id).strip("-") +if not safe_session_id: + safe_session_id = str(os.getpid()) + +payload_out = { + "hook_event_name": mapped_event, + "notification_type": mapped_ntype, + "cwd": cwd, + "session_id": f"codex-{safe_session_id}", + "permission_mode": str(payload_in.get("permission_mode", "")), + "source": "codex", +} + +summary = first_non_empty( + payload_in.get("last-assistant-message", ""), + payload_in.get("last_agent_message", ""), + payload_in.get("summary", ""), +) +if isinstance(summary, str) and summary: + payload_out["transcript_summary"] = summary[:120] + +error = first_non_empty(payload_in.get("error", ""), payload_in.get("message", "")) +if mapped_event == "PostToolUseFailure": + payload_out["tool_name"] = str(payload_in.get("tool_name", "") or payload_in.get("tool", "") or "Bash")[:64] + payload_out["error"] = str(error or f"Codex event: {raw_event}")[:180] + +print(json.dumps(payload_out)) +PY diff --git a/roles/codex/files/peon-config.json b/roles/codex/files/peon-config.json new file mode 100644 index 00000000..162e0d94 --- /dev/null +++ b/roles/codex/files/peon-config.json @@ -0,0 +1,20 @@ +{ + "default_pack": "jarvis-mk2", + "volume": 0.8, + "enabled": true, + "desktop_notifications": true, + "categories": { + "session.start": true, + "task.acknowledge": true, + "task.complete": true, + "task.error": true, + "input.required": true, + "resource.limit": true, + "user.spam": true + }, + "pack_rotation": [], + "pack_rotation_mode": "random", + "path_rules": [], + "notification_position": "top-center", + "notification_dismiss_seconds": 2 +} diff --git a/roles/codex/tasks/MacOSX.yml b/roles/codex/tasks/MacOSX.yml index 9f5e47da..e6cb2644 100644 --- a/roles/codex/tasks/MacOSX.yml +++ b/roles/codex/tasks/MacOSX.yml @@ -3,3 +3,8 @@ community.general.homebrew_cask: name: codex state: present + +- name: "{{ role_name }} | MacOSX | Install | peon-ping" + community.general.homebrew: + name: peon-ping + state: present diff --git a/roles/codex/tasks/Ubuntu.yml b/roles/codex/tasks/Ubuntu.yml index 1b92ebcc..23857a99 100644 --- a/roles/codex/tasks/Ubuntu.yml +++ b/roles/codex/tasks/Ubuntu.yml @@ -1,3 +1,60 @@ --- - name: "{{ role_name }} | Ubuntu | Install | Codex CLI" ansible.builtin.include_tasks: Linux.yml + +- name: "{{ role_name }} | Ubuntu | Install | Peon audio and notification dependencies" + become: true + ansible.builtin.apt: + name: + - ffmpeg + - libnotify-bin + state: present + update_cache: true + when: can_install_packages | default(false) + +- name: "{{ role_name }} | Ubuntu | Install | Peon dependencies skipped (no sudo)" + ansible.builtin.debug: + msg: + - "peon-ping dependency install skipped due to missing sudo privileges" + - "Ubuntu desktop sound notifications may not work until ffmpeg and libnotify-bin are installed" + when: not (can_install_packages | default(false)) + +- name: "{{ role_name }} | Ubuntu | Install | Check for managed Peon install" + ansible.builtin.stat: + path: "{{ codex_peon_install_dir }}/peon.sh" + register: codex_peon_ubuntu_install + +- name: "{{ role_name }} | Ubuntu | Install | Download Peon installer" + ansible.builtin.get_url: + url: "{{ codex_peon_install_script_url }}" + dest: "/tmp/peon-ping-install.sh" + mode: "0755" + when: + - not codex_peon_ubuntu_install.stat.exists + - not ansible_check_mode + +- name: "{{ role_name }} | Ubuntu | Install | Run official Peon installer" + ansible.builtin.command: + argv: + - bash + - /tmp/peon-ping-install.sh + - --global + - --no-rc + - "--packs={{ codex_peon_default_pack }}" + environment: + CLAUDE_CONFIG_DIR: "{{ codex_peon_install_base }}" + register: codex_peon_ubuntu_installer + changed_when: codex_peon_ubuntu_installer.rc == 0 + failed_when: false + when: + - not codex_peon_ubuntu_install.stat.exists + - not ansible_check_mode + +- name: "{{ role_name }} | Ubuntu | Install | Warn when Peon installer fails" + ansible.builtin.debug: + msg: + - "official peon-ping Ubuntu install failed" + - "{{ codex_peon_ubuntu_installer.stderr | default(codex_peon_ubuntu_installer.stdout | default('no output')) }}" + when: + - codex_peon_ubuntu_installer is defined + - codex_peon_ubuntu_installer.rc != 0 diff --git a/roles/codex/tasks/main.yml b/roles/codex/tasks/main.yml index fe4ced16..1fa41b7d 100644 --- a/roles/codex/tasks/main.yml +++ b/roles/codex/tasks/main.yml @@ -14,6 +14,113 @@ state: directory mode: "0755" +- name: "{{ role_name }} | Configure | Link Peon notify wrapper" + ansible.builtin.file: + src: "{{ codex_notify_script_source }}" + dest: "{{ codex_notify_script_dest }}" + state: link + force: true + when: codex_peon_enabled + +- name: "{{ role_name }} | Configure | Ensure Peon data directories exist" + ansible.builtin.file: + path: "{{ codex_peon_dir }}" + state: directory + mode: "0755" + when: codex_peon_enabled + +- name: "{{ role_name }} | Configure | Install Peon config" + ansible.builtin.copy: + src: "{{ codex_peon_config_source }}" + dest: "{{ codex_peon_config_dest }}" + mode: "0644" + when: codex_peon_enabled + +- name: "{{ role_name }} | Configure | Check if peon is installed" + ansible.builtin.command: which peon + register: codex_peon_path_check + check_mode: false + changed_when: false + failed_when: false + when: codex_peon_enabled + +- name: "{{ role_name }} | Configure | Check for managed Peon install" + ansible.builtin.stat: + path: "{{ codex_peon_install_dir }}/peon.sh" + register: codex_peon_managed_install + when: codex_peon_enabled + +- name: "{{ role_name }} | Configure | Link Peon packs from managed install" + ansible.builtin.file: + src: "{{ codex_peon_install_dir }}/packs" + dest: "{{ codex_peon_dir }}/packs" + state: link + force: true + when: + - codex_peon_enabled + - codex_peon_managed_install.stat.exists + +- name: "{{ role_name }} | Configure | Ensure Peon packs directory exists" + ansible.builtin.file: + path: "{{ codex_peon_dir }}/packs" + state: directory + mode: "0755" + when: + - codex_peon_enabled + - not codex_peon_managed_install.stat.exists + +- name: "{{ role_name }} | Configure | Set Peon CLI argv" + ansible.builtin.set_fact: + codex_peon_cli_argv: >- + {{ + ['/bin/bash', codex_peon_install_dir ~ '/peon.sh'] + if codex_peon_managed_install.stat.exists + else ['peon'] + }} + when: + - codex_peon_enabled + - codex_peon_managed_install.stat.exists or codex_peon_path_check.rc == 0 + +- name: "{{ role_name }} | Configure | Check for default Peon pack" + ansible.builtin.stat: + path: "{{ codex_peon_dir }}/packs/{{ codex_peon_default_pack }}/openpeon.json" + register: codex_peon_default_pack_manifest + when: + - codex_peon_enabled + - codex_peon_cli_argv is defined + +- name: "{{ role_name }} | Configure | Install default Peon pack" + ansible.builtin.command: + argv: "{{ codex_peon_cli_argv + ['packs', 'install', codex_peon_default_pack] }}" + environment: + CLAUDE_PEON_DIR: "{{ codex_peon_dir }}" + register: codex_peon_pack_install + changed_when: codex_peon_pack_install.rc == 0 + failed_when: false + when: + - codex_peon_enabled + - codex_peon_cli_argv is defined + - not codex_peon_default_pack_manifest.stat.exists + +- name: "{{ role_name }} | Configure | Warn when Peon is unavailable" + ansible.builtin.debug: + msg: + - "peon-ping is not installed for Codex" + - "Codex notifications will stay silent until peon-ping is installed" + when: + - codex_peon_enabled + - codex_peon_cli_argv is not defined + +- name: "{{ role_name }} | Configure | Warn when Peon pack install fails" + ansible.builtin.debug: + msg: + - "peon-ping pack install failed for {{ codex_peon_default_pack }}" + - "{{ codex_peon_pack_install.stderr | default(codex_peon_pack_install.stdout | default('no output')) }}" + when: + - codex_peon_enabled + - codex_peon_pack_install is defined + - codex_peon_pack_install.rc != 0 + - name: "{{ role_name }} | Skills | Ensure ~/.codex/skills directory exists" ansible.builtin.file: path: "{{ codex_skills_dest }}" From 066c77f4d4433577f561cd0b3058f9110f431169 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 11 Mar 2026 21:48:05 -0500 Subject: [PATCH 12/14] fix(neovim): refresh plugin and treesitter config --- roles/neovim/files/init.lua | 4 +- roles/neovim/files/lua/plugins/init.lua | 3 - roles/neovim/files/lua/plugins/lsp.lua | 30 ++-- roles/neovim/files/lua/plugins/telescope.lua | 11 +- .../lua/plugins/treesitter-textobjects.lua | 149 +++++++++--------- roles/neovim/files/lua/plugins/treesitter.lua | 6 +- 6 files changed, 97 insertions(+), 106 deletions(-) diff --git a/roles/neovim/files/init.lua b/roles/neovim/files/init.lua index 2bab283d..7bd95b2b 100644 --- a/roles/neovim/files/init.lua +++ b/roles/neovim/files/init.lua @@ -2,7 +2,8 @@ require('techdufus') -- Automatically install lazy.nvim local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" -if not vim.loop.fs_stat(lazypath) then +local uv = vim.uv or vim.loop +if not uv.fs_stat(lazypath) then vim.fn.system({ "git", "clone", @@ -42,4 +43,3 @@ require('lazy').setup('plugins', { }, }) - diff --git a/roles/neovim/files/lua/plugins/init.lua b/roles/neovim/files/lua/plugins/init.lua index 8a82e08c..39d12e23 100644 --- a/roles/neovim/files/lua/plugins/init.lua +++ b/roles/neovim/files/lua/plugins/init.lua @@ -2,11 +2,8 @@ return { "nvim-lua/popup.nvim", -- An implementation of the Popup API from vim in Neovim "nvim-lua/plenary.nvim", -- Useful lua functions used ny lots of plugins 'simrat39/symbols-outline.nvim', - { "windwp/nvim-ts-autotag", dependencies = "nvim-treesitter" }, - "neovim/nvim-lspconfig", ({ "mfussenegger/nvim-dap", dependencies = { "rcarriga/nvim-dap-ui" } }), "leoluz/nvim-dap-go", - "sharkdp/fd", { "sbdchd/neoformat", }, diff --git a/roles/neovim/files/lua/plugins/lsp.lua b/roles/neovim/files/lua/plugins/lsp.lua index 1a802cdd..024d02c0 100644 --- a/roles/neovim/files/lua/plugins/lsp.lua +++ b/roles/neovim/files/lua/plugins/lsp.lua @@ -161,10 +161,10 @@ return { vim.api.nvim_create_autocmd('LspAttach', { desc = 'LSP actions', callback = function(event) + local client = vim.lsp.get_client_by_id(event.data.client_id) local noremap = { buffer = event.buf, remap = false } local bind = vim.keymap.set - bind('n', 'r', 'lua vim.lsp.buf.rename()', noremap) -- Mappings. bind('n', 'gD', 'lua vim.lsp.buf.declaration()', noremap) bind('n', 'gd', 'lua vim.lsp.buf.definition()', noremap) @@ -175,13 +175,16 @@ return { bind('n', 'ca', 'lua vim.lsp.buf.code_action()', noremap) bind('n', 'gr', 'lua require("telescope.builtin").lsp_references()', noremap) bind('n', 'dl', 'Telescope diagnostics', noremap) - bind('n', 'ld', 'lua vim.diagnostic.open_float()', noremap) - bind('n', '[d', 'lua vim.lsp.diagnostic.goto_prev()', noremap) - bind('n', ']d', 'lua vim.lsp.diagnostic.goto_next()', noremap) - bind('n', 'q', 'lua vim.lsp.diagnostic.set_loclist()', noremap) + bind('n', 'ld', vim.diagnostic.open_float, noremap) + bind('n', '[d', function() vim.diagnostic.jump({ count = -1, float = true }) end, noremap) + bind('n', ']d', function() vim.diagnostic.jump({ count = 1, float = true }) end, noremap) + bind('n', 'q', vim.diagnostic.setloclist, noremap) bind("n", "f", "lua vim.lsp.buf.format({ async = true })", noremap) + if client and vim.lsp.inlay_hint and client:supports_method('textDocument/inlayHint') then + vim.lsp.inlay_hint.enable(true, { bufnr = event.buf }) + end -- if client is gopls then define bindings - if vim.lsp.get_client_by_id() == 'gopls' then + if client and client.name == 'gopls' then bind("n", "gtf", "GoTestFile", noremap) bind("n", "gtff", "GoTestFunc", noremap) bind("n", "gtt", "GoTest", noremap) @@ -204,9 +207,6 @@ return { 'lua_ls', 'yamlls', 'cssls', - 'gopls', - 'jsonls', - 'lua_ls', 'pylsp', }, handlers = { @@ -240,18 +240,6 @@ return { }) end }, - -- inlay hints - { - 'simrat39/inlay-hints.nvim', - config = function() - require("inlay-hints").setup({ - only_current_line = false, - eol = { - right_align = false, - } - }) - end - }, { "crispgm/nvim-go", dependencies = { diff --git a/roles/neovim/files/lua/plugins/telescope.lua b/roles/neovim/files/lua/plugins/telescope.lua index bb88b439..eec62a0a 100644 --- a/roles/neovim/files/lua/plugins/telescope.lua +++ b/roles/neovim/files/lua/plugins/telescope.lua @@ -9,7 +9,13 @@ return { "nvim-lua/plenary.nvim", "jvgrootveld/telescope-zoxide", "nvim-tree/nvim-web-devicons", - { 'nvim-telescope/telescope-fzf-native.nvim', run = 'make' }, + { + 'nvim-telescope/telescope-fzf-native.nvim', + build = 'make', + cond = function() + return vim.fn.executable('make') == 1 + end, + }, 'nvim-telescope/telescope-ui-select.nvim', }, keys = { @@ -243,6 +249,9 @@ return { -- telescope.load_extension('git_worktree'), }, } + pcall(telescope.load_extension, 'fzf') + pcall(telescope.load_extension, 'ui-select') + pcall(telescope.load_extension, 'zoxide') -- local M = {} -- builtin = require('telescope.builtin') diff --git a/roles/neovim/files/lua/plugins/treesitter-textobjects.lua b/roles/neovim/files/lua/plugins/treesitter-textobjects.lua index 8265802b..0ad781dd 100644 --- a/roles/neovim/files/lua/plugins/treesitter-textobjects.lua +++ b/roles/neovim/files/lua/plugins/treesitter-textobjects.lua @@ -2,100 +2,97 @@ return { "nvim-treesitter/nvim-treesitter-textobjects", lazy = true, config = function() - require("nvim-treesitter.configs").setup({ - textobjects = { - select = { - enable = true, - lookahead = true, - keymaps = { - ["a="] = { query = "@assignment.outer", desc = "Select outer part of an assignment" }, - ["i="] = { query = "@assignment.inner", desc = "Select inner part of an assignment" }, - ["l="] = { query = "@assignment.lhs", desc = "Select left hand side of an assignment" }, - ["r="] = { query = "@assignment.rhs", desc = "Select right hand side of an assignment" }, + require("nvim-treesitter-textobjects").setup({ + select = { + enable = true, + lookahead = true, + keymaps = { + ["a="] = { query = "@assignment.outer", desc = "Select outer part of an assignment" }, + ["i="] = { query = "@assignment.inner", desc = "Select inner part of an assignment" }, + ["l="] = { query = "@assignment.lhs", desc = "Select left hand side of an assignment" }, + ["r="] = { query = "@assignment.rhs", desc = "Select right hand side of an assignment" }, - ["aa"] = { query = "@parameter.outer", desc = "Select outer part of a parameter/argument" }, - ["ia"] = { query = "@parameter.inner", desc = "Select inner part of a parameter/argument" }, + ["aa"] = { query = "@parameter.outer", desc = "Select outer part of a parameter/argument" }, + ["ia"] = { query = "@parameter.inner", desc = "Select inner part of a parameter/argument" }, - ["ai"] = { query = "@conditional.outer", desc = "Select outer part of a conditional" }, - ["ii"] = { query = "@conditional.inner", desc = "Select inner part of a conditional" }, + ["ai"] = { query = "@conditional.outer", desc = "Select outer part of a conditional" }, + ["ii"] = { query = "@conditional.inner", desc = "Select inner part of a conditional" }, + ["al"] = { query = "@loop.outer", desc = "Select outer part of a loop" }, + ["il"] = { query = "@loop.inner", desc = "Select inner part of a loop" }, - ["al"] = { query = "@loop.outer", desc = "Select outer part of a loop" }, - ["il"] = { query = "@loop.inner", desc = "Select inner part of a loop" }, + ["af"] = { query = "@call.outer", desc = "Select outer part of a function call" }, + ["if"] = { query = "@call.inner", desc = "Select inner part of a function call" }, - ["af"] = { query = "@call.outer", desc = "Select outer part of a function call" }, - ["if"] = { query = "@call.inner", desc = "Select inner part of a function call" }, + ["am"] = { query = "@function.outer", desc = "Select outer part of a method/function definition" }, + ["im"] = { query = "@function.inner", desc = "Select inner part of a method/function definition" }, - ["am"] = { query = "@function.outer", desc = "Select outer part of a method/function definition" }, - ["im"] = { query = "@function.inner", desc = "Select inner part of a method/function definition" }, - - ["ac"] = { query = "@class.outer", desc = "Select outer part of a class" }, - ["ic"] = { query = "@class.inner", desc = "Select inner part of a class" }, - } + ["ac"] = { query = "@class.outer", desc = "Select outer part of a class" }, + ["ic"] = { query = "@class.inner", desc = "Select inner part of a class" }, + }, + }, + swap = { + enable = true, + swap_next = { + ["na"] = "@parameter.inner", -- swap parameters/argument with next + ["n:"] = "@property.outer", -- swap object property with next + ["nm"] = "@function.outer", -- swap function with next }, - swap = { - enable = true, - swap_next = { - ["na"] = "@parameter.inner", -- swap parameters/argument with next - ["n:"] = "@property.outer", -- swap object property with next - ["nm"] = "@function.outer", -- swap function with next - }, - swap_previous = { - ["pa"] = "@parameter.inner", -- swap parameters/argument with prev - ["p:"] = "@property.outer", -- swap object property with prev - ["pm"] = "@function.outer", -- swap function with previous - }, + swap_previous = { + ["pa"] = "@parameter.inner", -- swap parameters/argument with prev + ["p:"] = "@property.outer", -- swap object property with prev + ["pm"] = "@function.outer", -- swap function with previous }, - move = { - enable = true, - set_jumps = true, -- whether to set jumps in the jumplist - goto_next_start = { - ["]f"] = { query = "@call.outer", desc = "Next function call start" }, - ["]m"] = { query = "@function.outer", desc = "Next method/function def start" }, - ["]c"] = { query = "@class.outer", desc = "Next class start" }, - ["]i"] = { query = "@conditional.outer", desc = "Next conditional start" }, - ["]l"] = { query = "@loop.outer", desc = "Next loop start" }, + }, + move = { + enable = true, + set_jumps = true, -- whether to set jumps in the jumplist + goto_next_start = { + ["]f"] = { query = "@call.outer", desc = "Next function call start" }, + ["]m"] = { query = "@function.outer", desc = "Next method/function def start" }, + ["]c"] = { query = "@class.outer", desc = "Next class start" }, + ["]i"] = { query = "@conditional.outer", desc = "Next conditional start" }, + ["]l"] = { query = "@loop.outer", desc = "Next loop start" }, - -- You can pass a query group to use query from `queries//.scm file in your runtime path. - -- Below example nvim-treesitter's `locals.scm` and `folds.scm`. They also provide highlights.scm and indent.scm. - ["]s"] = { query = "@scope", query_group = "locals", desc = "Next scope" }, - ["]z"] = { query = "@fold", query_group = "folds", desc = "Next fold" }, - }, - goto_next_end = { - ["]F"] = { query = "@call.outer", desc = "Next function call end" }, - ["]M"] = { query = "@function.outer", desc = "Next method/function def end" }, - ["]C"] = { query = "@class.outer", desc = "Next class end" }, - ["]I"] = { query = "@conditional.outer", desc = "Next conditional end" }, - ["]L"] = { query = "@loop.outer", desc = "Next loop end" }, - }, - goto_previous_start = { - ["[f"] = { query = "@call.outer", desc = "Prev function call start" }, - ["[m"] = { query = "@function.outer", desc = "Prev method/function def start" }, - ["[c"] = { query = "@class.outer", desc = "Prev class start" }, - ["[i"] = { query = "@conditional.outer", desc = "Prev conditional start" }, - ["[l"] = { query = "@loop.outer", desc = "Prev loop start" }, - }, - goto_previous_end = { - ["[F"] = { query = "@call.outer", desc = "Prev function call end" }, - ["[M"] = { query = "@function.outer", desc = "Prev method/function def end" }, - ["[C"] = { query = "@class.outer", desc = "Prev class end" }, - ["[I"] = { query = "@conditional.outer", desc = "Prev conditional end" }, - ["[L"] = { query = "@loop.outer", desc = "Prev loop end" }, - }, + -- You can pass a query group to use query from `queries//.scm file in your runtime path. + -- Below example nvim-treesitter's `locals.scm` and `folds.scm`. They also provide highlights.scm and indent.scm. + ["]s"] = { query = "@scope", query_group = "locals", desc = "Next scope" }, + ["]z"] = { query = "@fold", query_group = "folds", desc = "Next fold" }, + }, + goto_next_end = { + ["]F"] = { query = "@call.outer", desc = "Next function call end" }, + ["]M"] = { query = "@function.outer", desc = "Next method/function def end" }, + ["]C"] = { query = "@class.outer", desc = "Next class end" }, + ["]I"] = { query = "@conditional.outer", desc = "Next conditional end" }, + ["]L"] = { query = "@loop.outer", desc = "Next loop end" }, + }, + goto_previous_start = { + ["[f"] = { query = "@call.outer", desc = "Prev function call start" }, + ["[m"] = { query = "@function.outer", desc = "Prev method/function def start" }, + ["[c"] = { query = "@class.outer", desc = "Prev class start" }, + ["[i"] = { query = "@conditional.outer", desc = "Prev conditional start" }, + ["[l"] = { query = "@loop.outer", desc = "Prev loop start" }, + }, + goto_previous_end = { + ["[F"] = { query = "@call.outer", desc = "Prev function call end" }, + ["[M"] = { query = "@function.outer", desc = "Prev method/function def end" }, + ["[C"] = { query = "@class.outer", desc = "Prev class end" }, + ["[I"] = { query = "@conditional.outer", desc = "Prev conditional end" }, + ["[L"] = { query = "@loop.outer", desc = "Prev loop end" }, }, }, }) - local ts_repeat_move = require("nvim-treesitter.textobjects.repeatable_move") + local ts_repeat_move = require("nvim-treesitter-textobjects.repeatable_move") -- vim way: ; goes to the direction you were moving. vim.keymap.set({ "n", "x", "o" }, ";", ts_repeat_move.repeat_last_move) vim.keymap.set({ "n", "x", "o" }, ",", ts_repeat_move.repeat_last_move_opposite) -- Optionally, make builtin f, F, t, T also repeatable with ; and , - vim.keymap.set({ "n", "x", "o" }, "f", ts_repeat_move.builtin_f) - vim.keymap.set({ "n", "x", "o" }, "F", ts_repeat_move.builtin_F) - vim.keymap.set({ "n", "x", "o" }, "t", ts_repeat_move.builtin_t) - vim.keymap.set({ "n", "x", "o" }, "T", ts_repeat_move.builtin_T) + vim.keymap.set({ "n", "x", "o" }, "f", ts_repeat_move.builtin_f_expr, { expr = true }) + vim.keymap.set({ "n", "x", "o" }, "F", ts_repeat_move.builtin_F_expr, { expr = true }) + vim.keymap.set({ "n", "x", "o" }, "t", ts_repeat_move.builtin_t_expr, { expr = true }) + vim.keymap.set({ "n", "x", "o" }, "T", ts_repeat_move.builtin_T_expr, { expr = true }) end, } diff --git a/roles/neovim/files/lua/plugins/treesitter.lua b/roles/neovim/files/lua/plugins/treesitter.lua index a159dcca..2cc4fe7f 100644 --- a/roles/neovim/files/lua/plugins/treesitter.lua +++ b/roles/neovim/files/lua/plugins/treesitter.lua @@ -9,7 +9,7 @@ return { "nvim-treesitter/nvim-treesitter-textobjects", }, config = function() - require 'nvim-treesitter.configs'.setup { + require('nvim-treesitter.configs').setup({ -- A list of parser names, or "all" ensure_installed = "all", -- Ignore parsers that are known to have issues @@ -41,7 +41,7 @@ return { scope_incremental = false, node_decremental = "", }, - } - } + }, + }) end } From 99c736920a423df4ccd2ac4f2cc36fa0253517a5 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 11 Mar 2026 21:50:42 -0500 Subject: [PATCH 13/14] fix(codex): guard peon warning tasks --- roles/codex/tasks/Ubuntu.yml | 3 ++- roles/codex/tasks/main.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/roles/codex/tasks/Ubuntu.yml b/roles/codex/tasks/Ubuntu.yml index 23857a99..c4fbb917 100644 --- a/roles/codex/tasks/Ubuntu.yml +++ b/roles/codex/tasks/Ubuntu.yml @@ -57,4 +57,5 @@ - "{{ codex_peon_ubuntu_installer.stderr | default(codex_peon_ubuntu_installer.stdout | default('no output')) }}" when: - codex_peon_ubuntu_installer is defined - - codex_peon_ubuntu_installer.rc != 0 + - not (codex_peon_ubuntu_installer.skipped | default(false)) + - (codex_peon_ubuntu_installer.rc | default(0)) != 0 diff --git a/roles/codex/tasks/main.yml b/roles/codex/tasks/main.yml index 1fa41b7d..cdf3a0dc 100644 --- a/roles/codex/tasks/main.yml +++ b/roles/codex/tasks/main.yml @@ -119,7 +119,8 @@ when: - codex_peon_enabled - codex_peon_pack_install is defined - - codex_peon_pack_install.rc != 0 + - not (codex_peon_pack_install.skipped | default(false)) + - (codex_peon_pack_install.rc | default(0)) != 0 - name: "{{ role_name }} | Skills | Ensure ~/.codex/skills directory exists" ansible.builtin.file: From ce285964c4d510f4e372721d42d9d812de246784 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 11 Mar 2026 22:06:39 -0500 Subject: [PATCH 14/14] fix(opencode): escape review command globs --- roles/opencode/files/command/gh-review.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/roles/opencode/files/command/gh-review.md b/roles/opencode/files/command/gh-review.md index 9de334aa..2481c145 100644 --- a/roles/opencode/files/command/gh-review.md +++ b/roles/opencode/files/command/gh-review.md @@ -112,13 +112,13 @@ Create independent agents with non-overlapping file patterns. Launch multiple @general subagent calls: -@general **Review Backend Files** for PR $ARGUMENTS. Focus on files in api/*, models/*, services/*. Analyze: business logic correctness, error handling patterns, security vulnerabilities, database query efficiency. Return: list of issues (critical/high/medium/low), specific line references, code improvement suggestions. +@general **Review Backend Files** for PR $ARGUMENTS. Focus on files in `api/*`, `models/*`, `services/*`. Analyze: business logic correctness, error handling patterns, security vulnerabilities, database query efficiency. Return: list of issues (critical/high/medium/low), specific line references, code improvement suggestions. -@general **Review Frontend Files** for PR $ARGUMENTS. Focus on files in src/components/*, src/pages/*, *.tsx, *.jsx. Analyze: React patterns, state management, performance issues, accessibility. Return: list of issues (critical/high/medium/low), specific line references, code improvement suggestions. +@general **Review Frontend Files** for PR $ARGUMENTS. Focus on files in `src/components/*`, `src/pages/*`, `*.tsx`, `*.jsx`. Analyze: React patterns, state management, performance issues, accessibility. Return: list of issues (critical/high/medium/low), specific line references, code improvement suggestions. -@general **Review Test Files** for PR $ARGUMENTS. Focus on files in tests/*, __tests__/*, *.test.*, *.spec.*. Analyze: test coverage gaps, edge cases not tested, test quality, mock appropriateness. Return: list of issues (critical/high/medium/low), coverage recommendations. +@general **Review Test Files** for PR $ARGUMENTS. Focus on files in `tests/*`, `__tests__/*`, `*.test.*`, `*.spec.*`. Analyze: test coverage gaps, edge cases not tested, test quality, mock appropriateness. Return: list of issues (critical/high/medium/low), coverage recommendations. -@general **Review Infrastructure Files** for PR $ARGUMENTS. Focus on files in .github/*, docker/*, *.yml, *.yaml, config/*. Analyze: CI/CD configuration, security best practices, deployment concerns. Return: list of issues (critical/high/medium/low), security findings. +@general **Review Infrastructure Files** for PR $ARGUMENTS. Focus on files in `.github/*`, `docker/*`, `*.yml`, `*.yaml`, `config/*`. Analyze: CI/CD configuration, security best practices, deployment concerns. Return: list of issues (critical/high/medium/low), security findings. **Step 4: Synthesize Agent Findings**