From 954d41425b85e1c432f24a42b5d617d101a1a244 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 05:57:49 +0000 Subject: [PATCH 01/18] Revive terminal dotfiles setup --- README.md | 123 +++++++++++++++++++--- bin/dotfiles-clipboard-copy | 23 +++++ install.sh | 60 ++--------- mpd/mpd-macos.conf | 19 ++++ ncmpcpp/config-macos | 20 ++++ nvim/init.lua | 98 ++++++++++++++++++ ranger/rc.conf | 6 +- setup.sh | 197 ++++++++++++++++++++++++++++++++++++ vim/vimrc-basic | 37 +++++++ 9 files changed, 517 insertions(+), 66 deletions(-) create mode 100755 bin/dotfiles-clipboard-copy create mode 100644 mpd/mpd-macos.conf create mode 100644 ncmpcpp/config-macos create mode 100644 nvim/init.lua create mode 100755 setup.sh create mode 100644 vim/vimrc-basic diff --git a/README.md b/README.md index a2a8482..0b1dcca 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,116 @@ -All my dotfiles at one place. +# Dotfiles -I will delete the i3 and vim dotfiles repos soon. -I have merged them with this repo, so the version history -of the two repos is saved in this repo. +Personal terminal-environment config for machines that should feel familiar: +music, editor, file navigation, and other CLI workflows. -ranger for file navigation. +This repo is being revived in stages. The current v1 target is intentionally +small: -mpd and ncmpcpp for music (command line) +- macOS music playback with `mpd`, `ncmpcpp`, and local files under + `/Users/sourya4/pro/youtube_download/music` +- baseline Neovim config, plus a minimal Vim fallback +- AI-enabled Neovim chat through CodeCompanion and Codex ACP +- ranger config with portable clipboard helpers -vimperator is a firefox extension +`ma08/botfiles` remains the home for coding-agent, orchestration, hooks, skills, +and automation bootstrap. This repo should own user-facing terminal app config. -The present state of repo is not suitable for anyone to follow. -Still, if you are interested, you can have a peek. I will clean up -and write a more descriptive README later outlining all the features and -the setup process. +## Quick Start -Look into install.sh to get an idea of the setup (regarding symlinks and git - submodules for vim plugins). +Preview everything first: -![alt tag](screen.png) +```bash +./setup.sh --all +``` + +Apply the current v1 config: + +```bash +./setup.sh --all --apply +``` + +Install the macOS music packages and apply music config: + +```bash +./setup.sh --music --install-packages --apply +``` + +Install packages for the full v1 profile and apply all current config: + +```bash +./setup.sh --all --install-packages --apply +``` + +The setup script backs up existing non-matching files before replacing them with +symlinks. Without `--apply`, it only prints what it would do. + +## Music + +The macOS music profile installs or configures: + +- `mpd` +- `mpc` +- `ncmpcpp` + +Relevant paths: + +- MPD config: `~/.mpd/mpd.conf` +- MPD music directory: `/Users/sourya4/pro/youtube_download/music` +- ncmpcpp config: `~/.ncmpcpp/config` +- ncmpcpp bindings: `~/.ncmpcpp/bindings` + +Basic checks: + +```bash +mpd --no-daemon ~/.mpd/mpd.conf +mpc update +ncmpcpp +``` + +Terminal playback is the v1 success gate. macOS media keys / Now Playing support +is a follow-up unless it becomes simple and reliable. + +## Editor + +Neovim is the primary editor target: + +- `nvim/init.lua` -> `~/.config/nvim/init.lua` + +With `--install-packages`, the editor profile installs `neovim` through +Homebrew, plus `node` for `npx`-backed ACP adapters. + +Vim gets a minimal fallback: + +- `vim/vimrc-basic` -> `~/.vimrc` + +The old `vim/.vimrc` and Vim bundle submodules are historical material, not the +default install path for the revived setup. + +### AI In Neovim + +The Neovim config bootstraps `lazy.nvim`, installs CodeCompanion, and configures +the Codex ACP adapter with ChatGPT authentication. It runs the Zed Codex ACP +adapter through: + +```bash +npx -y @zed-industries/codex-acp +``` + +Use `:CodeCompanionChat` inside Neovim to open an AI chat buffer. The first run +may download plugins and the ACP adapter. ChatGPT auth must be available through +Codex/Codex ACP on the machine; API-key and Azure OpenAI variants can be added +later without changing the base music/editor/ranger setup. + +## Ranger + +The ranger profile links the repo's ranger config into `~/.config/ranger/` and +installs `bin/dotfiles-clipboard-copy` into `~/.local/bin/`. The clipboard helper +uses `pbcopy`, `wl-copy`, `xclip`, or `xsel`, depending on what the machine has. +With `--install-packages`, the ranger profile installs `ranger` through Homebrew. + +## Historical Material + +The old repo includes i3, polybar, Vimperator, X resources, old Linux desktop +scripts, and large Vim plugin submodule history. Those are preserved for now but +not installed by the default v1 setup. Audit and resurrect them only after an +explicit review. diff --git a/bin/dotfiles-clipboard-copy b/bin/dotfiles-clipboard-copy new file mode 100755 index 0000000..af39c82 --- /dev/null +++ b/bin/dotfiles-clipboard-copy @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v pbcopy >/dev/null 2>&1; then + exec pbcopy +fi + +if command -v wl-copy >/dev/null 2>&1; then + exec wl-copy +fi + +if command -v xclip >/dev/null 2>&1; then + exec xclip -selection clipboard +fi + +if command -v xsel >/dev/null 2>&1; then + exec xsel -ib +fi + +cat >/dev/null +echo "dotfiles-clipboard-copy: no clipboard command found" >&2 +exit 1 + diff --git a/install.sh b/install.sh index 3ed3ab2..dae5809 100755 --- a/install.sh +++ b/install.sh @@ -1,55 +1,17 @@ -sudo apt install rofi feh compton rxvt-unicode-256color -sudo apt-get install clipit +#!/usr/bin/env bash +set -euo pipefail -git submodule init -git submodule update -#git submodule foreach git submodule init -#git submodule foreach git submodule update -git submodule foreach git submodule update --init --recursive +cat >&2 <<'MSG' +The old install.sh was destructive and Linux-specific. -cp -R ~/.vim ~/.vimdup -cp -R ~/.i3 ~/.i3dup -cp -R ~/.config/ranger ~/.config/rangerdup -cp -R ~/.mpd ~/.mpddup -cp -R ~/.ncmpcpp ~/.ncmpcppdup -cp -R ~/.vimperator ~/.vimperatordup +Use the safer revived setup entrypoint instead: -cp ~/.vimperatorrc ~/.vimperatorrcdup -cp ~/.vimrc ~/.vimrcdup -cp ~/.gvimrc ~/.gvimrcdup -cp ~/.i3status.conf ~/.i3status.confdup -cp ~/.Xresources ~/.Xresourcesdup + ./setup.sh --all # dry run + ./setup.sh --all --apply # apply symlinks/backups +For macOS music setup: -rm ~/.vimperatorrc -rm ~/.vimrc -rm ~/.Xresources -rm ~/.gvimrc -rm ~/.i3status.conf + ./setup.sh --music --install-packages --apply +MSG -rm -rf ~/.vim -rm -rf ~/.i3 -rm -rf ~/.mpd -rm -rf ~/.ncmpcpp -rm -rf ~/.vimperator -rm -rf ~/.config/ranger - - -ln -s $(pwd)/ranger ~/.config/ranger -ln -s $(pwd)/ncmpcpp ~/.ncmpcpp -ln -s $(pwd)/mpd ~/.mpd -ln -s $(pwd)/vimperator ~/.vimperator -ln -s $(pwd)/vim ~/.vim -ln -s $(pwd)/i3 ~/.i3 - -ln -s $(pwd)/bashrc ~/.bashrc -ln -s $(pwd)/Xresources ~/.Xresources -ln -s $(pwd)/vim/.vimrc ~/.vimrc -ln -s $(pwd)/vscode/.vimrc ~/.vscode_vimrc -ln -s $(pwd)/vim/.gvimrc ~/.gvimrc -ln -s $(pwd)/vimperator/.vimperatorrc ~/.vimperatorrc -ln -s $(pwd)/i3/i3status.conf ~/.i3status.conf - - -#cd ~/.vim/bundle/YouCompleteMe -#./install.sh --clang-completer --system-libclang --system-boost +exit 1 diff --git a/mpd/mpd-macos.conf b/mpd/mpd-macos.conf new file mode 100644 index 0000000..bc20d38 --- /dev/null +++ b/mpd/mpd-macos.conf @@ -0,0 +1,19 @@ +music_directory "/Users/sourya4/pro/youtube_download/music" +playlist_directory "~/.mpd/playlists" +db_file "~/.mpd/database" +log_file "~/.mpd/log" +pid_file "~/.mpd/pid" +state_file "~/.mpd/state" +sticker_file "~/.mpd/sticker.sql" + +bind_to_address "127.0.0.1" +port "6600" +auto_update "yes" +restore_paused "yes" + +audio_output { + type "osx" + name "CoreAudio" + mixer_type "software" +} + diff --git a/ncmpcpp/config-macos b/ncmpcpp/config-macos new file mode 100644 index 0000000..b01b7d2 --- /dev/null +++ b/ncmpcpp/config-macos @@ -0,0 +1,20 @@ +mpd_host = "127.0.0.1" +mpd_port = "6600" +mpd_music_dir = "/Users/sourya4/pro/youtube_download/music" + +user_interface = "alternative" +browser_display_mode = "columns" +search_engine_display_mode = "columns" +playlist_display_mode = "columns" +playlist_editor_display_mode = "columns" + +song_list_format = "{$8%a - $9}{$3%t$9}|{$8%f$9}" +song_columns_list_format = "(30)[white]{l} (45)[yellow]{a} (60)[cyan]{t} (40)[red]{b}" +media_library_primary_tag = "album_artist" + +display_bitrate = "yes" +display_remaining_time = "yes" +cyclic_scrolling = "yes" +follow_now_playing_lyrics = "yes" +ignore_leading_the = "yes" + diff --git a/nvim/init.lua b/nvim/init.lua new file mode 100644 index 0000000..082e083 --- /dev/null +++ b/nvim/init.lua @@ -0,0 +1,98 @@ +vim.g.mapleader = "," + +local opt = vim.opt + +opt.encoding = "utf-8" +opt.number = true +opt.ruler = true +opt.showcmd = true +opt.hidden = true +opt.confirm = true +opt.autoread = true +opt.backspace = { "indent", "eol", "start" } +opt.mouse = "a" + +opt.tabstop = 4 +opt.shiftwidth = 4 +opt.softtabstop = 4 +opt.expandtab = true +opt.smartindent = true + +opt.ignorecase = true +opt.smartcase = true +opt.incsearch = true +opt.hlsearch = true +opt.wrapscan = true + +opt.splitbelow = true +opt.splitright = true +opt.wildmenu = true +opt.wildmode = { "longest:full", "full" } +opt.timeoutlen = 400 +opt.updatetime = 300 + +opt.undofile = true +opt.undodir = vim.fn.expand("~/.local/state/nvim/undo//") + +if vim.fn.has("clipboard") == 1 then + opt.clipboard = "unnamedplus" +end + +vim.cmd("filetype plugin indent on") +vim.cmd("syntax enable") + +vim.keymap.set("i", "jk", "", { silent = true }) +vim.keymap.set("v", "<", "", ">gv", { silent = true }) +vim.keymap.set("n", "h", "nohlsearch", { silent = true }) +vim.keymap.set("n", "", "bprevious", { silent = true }) +vim.keymap.set("n", "", "bnext", { silent = true }) + +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.uv.fs_stat(lazypath) then + vim.fn.system({ + "git", + "clone", + "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", + lazypath, + }) +end +vim.opt.rtp:prepend(lazypath) + +require("lazy").setup({ + { + "olimorris/codecompanion.nvim", + dependencies = { + "nvim-lua/plenary.nvim", + "nvim-treesitter/nvim-treesitter", + }, + opts = { + adapters = { + acp = { + codex = function() + return require("codecompanion.adapters").extend("codex", { + defaults = { + auth_method = "chatgpt", + }, + commands = { + default = { + "npx", + "-y", + "@zed-industries/codex-acp", + }, + }, + }) + end, + }, + }, + interactions = { + chat = { + adapter = "codex", + }, + }, + }, + }, +}) + diff --git a/ranger/rc.conf b/ranger/rc.conf index a59c083..10e01c1 100644 --- a/ranger/rc.conf +++ b/ranger/rc.conf @@ -265,9 +265,9 @@ map g/ cd / map E edit map du shell -p du --max-depth=1 -h --apparent-size map dU shell -p du --max-depth=1 -h --apparent-size | sort -rh -map yp shell -d echo -n %d/%f | xsel -i -map yd shell -d echo -n %d | xsel -i -map yn shell -d echo -n %f | xsel -i +map yp shell -d printf '%s' '%d/%f' | dotfiles-clipboard-copy +map yd shell -d printf '%s' '%d' | dotfiles-clipboard-copy +map yn shell -d printf '%s' '%f' | dotfiles-clipboard-copy # Filesystem Operations map = chmod diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..e847fb0 --- /dev/null +++ b/setup.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APPLY=0 +INSTALL_PACKAGES=0 +DO_MUSIC=0 +DO_EDITOR=0 +DO_RANGER=0 + +usage() { + cat <<'USAGE' +Usage: ./setup.sh [--all] [--music] [--editor] [--ranger] [--install-packages] [--apply] + +By default this is a dry run. Pass --apply to make changes. + +Profiles: + --music Configure mpd/ncmpcpp + --editor Configure Neovim and Vim fallback + --ranger Configure ranger and clipboard helper + --all Configure all current v1 profiles + +Options: + --install-packages Install Homebrew packages needed by selected profiles + --apply Make changes; otherwise only print actions + -h, --help Show this help +USAGE +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --all) + DO_MUSIC=1 + DO_EDITOR=1 + DO_RANGER=1 + ;; + --music) DO_MUSIC=1 ;; + --editor) DO_EDITOR=1 ;; + --ranger) DO_RANGER=1 ;; + --install-packages) INSTALL_PACKAGES=1 ;; + --apply) APPLY=1 ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "ERROR: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac + shift +done + +if [ "$DO_MUSIC$DO_EDITOR$DO_RANGER" = "000" ]; then + usage >&2 + exit 2 +fi + +log() { + printf '%s\n' "$*" +} + +run() { + if [ "$APPLY" -eq 1 ]; then + "$@" + else + printf '[dry-run] ' + printf '%q ' "$@" + printf '\n' + fi +} + +timestamp() { + date +%Y%m%d%H%M%S +} + +backup_path() { + local path="$1" + if [ -e "$path" ] || [ -L "$path" ]; then + run mv "$path" "${path}.dotfiles-backup.$(timestamp)" + fi +} + +ensure_dir() { + run mkdir -p "$1" +} + +touch_file() { + if [ ! -e "$1" ]; then + run touch "$1" + fi +} + +link_path() { + local source="$1" + local dest="$2" + + if [ ! -e "$source" ] && [ ! -L "$source" ]; then + echo "ERROR: source does not exist: $source" >&2 + exit 1 + fi + + ensure_dir "$(dirname "$dest")" + + if [ -L "$dest" ] && [ "$(readlink "$dest")" = "$source" ]; then + log "already linked: $dest -> $source" + return + fi + + backup_path "$dest" + run ln -s "$source" "$dest" +} + +brew_bin() { + if command -v brew >/dev/null 2>&1; then + command -v brew + elif [ -x /opt/homebrew/bin/brew ]; then + printf '%s\n' /opt/homebrew/bin/brew + elif [ -x /usr/local/bin/brew ]; then + printf '%s\n' /usr/local/bin/brew + else + return 1 + fi +} + +install_brew_packages() { + local brew + if [ "$APPLY" -eq 0 ]; then + run brew install "$@" + return + fi + + brew="$(brew_bin)" || { + echo "ERROR: Homebrew not found; install packages manually or rerun without --install-packages" >&2 + exit 1 + } + run "$brew" install "$@" +} + +setup_music() { + log "== music ==" + + if [ "$INSTALL_PACKAGES" -eq 1 ]; then + install_brew_packages mpd mpc ncmpcpp + fi + + ensure_dir "$HOME/.mpd/playlists" + touch_file "$HOME/.mpd/database" + touch_file "$HOME/.mpd/log" + touch_file "$HOME/.mpd/pid" + touch_file "$HOME/.mpd/state" + touch_file "$HOME/.mpd/sticker.sql" + ensure_dir "$HOME/.ncmpcpp" + + link_path "$ROOT/mpd/mpd-macos.conf" "$HOME/.mpd/mpd.conf" + link_path "$ROOT/ncmpcpp/config-macos" "$HOME/.ncmpcpp/config" + link_path "$ROOT/ncmpcpp/bindings" "$HOME/.ncmpcpp/bindings" +} + +setup_editor() { + log "== editor ==" + if [ "$INSTALL_PACKAGES" -eq 1 ]; then + install_brew_packages neovim node + fi + + ensure_dir "$HOME/.local/state/nvim/undo" + if [ -e "$HOME/.config/nvim/init.vim" ] || [ -L "$HOME/.config/nvim/init.vim" ]; then + backup_path "$HOME/.config/nvim/init.vim" + fi + link_path "$ROOT/nvim/init.lua" "$HOME/.config/nvim/init.lua" + link_path "$ROOT/vim/vimrc-basic" "$HOME/.vimrc" +} + +setup_ranger() { + log "== ranger ==" + if [ "$INSTALL_PACKAGES" -eq 1 ]; then + install_brew_packages ranger + fi + + link_path "$ROOT/bin/dotfiles-clipboard-copy" "$HOME/.local/bin/dotfiles-clipboard-copy" + link_path "$ROOT/ranger/rc.conf" "$HOME/.config/ranger/rc.conf" + link_path "$ROOT/ranger/rifle.conf" "$HOME/.config/ranger/rifle.conf" + link_path "$ROOT/ranger/commands.py" "$HOME/.config/ranger/commands.py" + link_path "$ROOT/ranger/scope.sh" "$HOME/.config/ranger/scope.sh" + link_path "$ROOT/ranger/colorschemes" "$HOME/.config/ranger/colorschemes" +} + +if [ "$APPLY" -eq 0 ]; then + log "Dry run only. Re-run with --apply to make changes." +fi + +[ "$DO_MUSIC" -eq 1 ] && setup_music +[ "$DO_EDITOR" -eq 1 ] && setup_editor +[ "$DO_RANGER" -eq 1 ] && setup_ranger + +log "Done." diff --git a/vim/vimrc-basic b/vim/vimrc-basic new file mode 100644 index 0000000..d795c41 --- /dev/null +++ b/vim/vimrc-basic @@ -0,0 +1,37 @@ +set nocompatible +filetype plugin indent on +syntax enable + +set encoding=utf-8 +set number +set ruler +set showcmd +set confirm +set autoread +set backspace=indent,eol,start + +set tabstop=4 +set shiftwidth=4 +set softtabstop=4 +set expandtab +set smartindent + +set ignorecase +set smartcase +set incsearch +set hlsearch +set wrapscan +set wildmenu +set timeoutlen=400 + +if has('clipboard') + set clipboard=unnamed +endif + +let mapleader="," + +inoremap jk +vnoremap < >gv +nnoremap h :nohlsearch + From 53f1a0be573412f2af7e7944f472178d692e2ee8 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 06:16:17 +0000 Subject: [PATCH 02/18] Disable crashing default ncmpcpp browser key --- ncmpcpp/bindings | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ncmpcpp/bindings b/ncmpcpp/bindings index ac05019..05532f1 100644 --- a/ncmpcpp/bindings +++ b/ncmpcpp/bindings @@ -28,6 +28,14 @@ def_key "G" move_end def_key "p" pause +def_key "tab" + dummy +def_key "shift-tab" + dummy +def_key "2" + dummy +def_key "B" + show_browser def_key "x" delete_playlist_items def_key "n" From 9f7360684d94482ab08758ab2a454c7d5288c3eb Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 15:15:20 +0000 Subject: [PATCH 03/18] Restore ncmpcpp browser navigation bindings --- ncmpcpp/bindings | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ncmpcpp/bindings b/ncmpcpp/bindings index 05532f1..e42a442 100644 --- a/ncmpcpp/bindings +++ b/ncmpcpp/bindings @@ -28,12 +28,6 @@ def_key "G" move_end def_key "p" pause -def_key "tab" - dummy -def_key "shift-tab" - dummy -def_key "2" - dummy def_key "B" show_browser def_key "x" From af50155576bab67d3e8c9cf21b711040fa9f5361 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 18:06:34 +0000 Subject: [PATCH 04/18] Add ncmpcpp visualizer and mac media keys --- hammerspoon/init.lua | 68 ++++++++++++++++++++++++++++++++++++++++++++ mpd/mpd-macos.conf | 6 ++++ ncmpcpp/config-macos | 11 +++++++ setup.sh | 16 +++++++++++ 4 files changed, 101 insertions(+) create mode 100644 hammerspoon/init.lua diff --git a/hammerspoon/init.lua b/hammerspoon/init.lua new file mode 100644 index 0000000..2c226a3 --- /dev/null +++ b/hammerspoon/init.lua @@ -0,0 +1,68 @@ +local mpc = "/opt/homebrew/bin/mpc" +local mpdHost = "127.0.0.1" +local mpdPort = "6600" + +local function shellQuote(value) + return "'" .. tostring(value):gsub("'", "'\\''") .. "'" +end + +local function mpcCommand(args) + return table.concat({ + shellQuote(mpc), + "-h", + shellQuote(mpdHost), + "-p", + shellQuote(mpdPort), + args, + }, " ") +end + +local function runMpc(args) + hs.execute(mpcCommand(args) .. " >/dev/null 2>&1", true) +end + +local function mpdHasQueue() + local output, ok = hs.execute(mpcCommand("playlist"), true) + return ok and output ~= nil and output:match("%S") ~= nil +end + +local mediaActions = { + PLAY = "toggle", + NEXT = "next", + PREVIOUS = "prev", +} + +local ipc = require("hs.ipc") +pcall(function() + ipc.cliInstall("/opt/homebrew", true) +end) +hs.autoLaunch(true) + +local function startMpdMediaKeys() + local mediaKeyTap = hs.eventtap.new({ hs.eventtap.event.types.systemDefined }, function(event) + local systemKey = event:systemKey() + if systemKey == nil then + return false + end + + local action = mediaActions[systemKey.key] + if action == nil or not mpdHasQueue() then + return false + end + + if systemKey.down and not systemKey["repeat"] then + runMpc(action) + end + + return true + end) + + mediaKeyTap:start() + return mediaKeyTap +end + +if hs.accessibilityState(true) then + mpdMediaKeyTap = startMpdMediaKeys() +else + hs.alert.show("Enable Accessibility for Hammerspoon to control MPD media keys") +end diff --git a/mpd/mpd-macos.conf b/mpd/mpd-macos.conf index bc20d38..4b37069 100644 --- a/mpd/mpd-macos.conf +++ b/mpd/mpd-macos.conf @@ -17,3 +17,9 @@ audio_output { mixer_type "software" } +audio_output { + type "fifo" + name "Visualizer feed" + path "/tmp/mpd.fifo" + format "44100:16:2" +} diff --git a/ncmpcpp/config-macos b/ncmpcpp/config-macos index b01b7d2..3a09a58 100644 --- a/ncmpcpp/config-macos +++ b/ncmpcpp/config-macos @@ -18,3 +18,14 @@ cyclic_scrolling = "yes" follow_now_playing_lyrics = "yes" ignore_leading_the = "yes" +visualizer_data_source = "/tmp/mpd.fifo" +visualizer_output_name = "Visualizer feed" +visualizer_in_stereo = "yes" +visualizer_type = "spectrum" +visualizer_fps = "60" +visualizer_autoscale = "yes" +visualizer_spectrum_smooth_look = "yes" +visualizer_spectrum_dft_size = "2" +visualizer_spectrum_gain = "10" +visualizer_spectrum_log_scale_x = "yes" +visualizer_spectrum_log_scale_y = "yes" diff --git a/setup.sh b/setup.sh index e847fb0..11ec574 100755 --- a/setup.sh +++ b/setup.sh @@ -138,11 +138,26 @@ install_brew_packages() { run "$brew" install "$@" } +install_brew_casks() { + local brew + if [ "$APPLY" -eq 0 ]; then + run brew install --cask "$@" + return + fi + + brew="$(brew_bin)" || { + echo "ERROR: Homebrew not found; install casks manually or rerun without --install-packages" >&2 + exit 1 + } + run "$brew" install --cask "$@" +} + setup_music() { log "== music ==" if [ "$INSTALL_PACKAGES" -eq 1 ]; then install_brew_packages mpd mpc ncmpcpp + install_brew_casks hammerspoon fi ensure_dir "$HOME/.mpd/playlists" @@ -156,6 +171,7 @@ setup_music() { link_path "$ROOT/mpd/mpd-macos.conf" "$HOME/.mpd/mpd.conf" link_path "$ROOT/ncmpcpp/config-macos" "$HOME/.ncmpcpp/config" link_path "$ROOT/ncmpcpp/bindings" "$HOME/.ncmpcpp/bindings" + link_path "$ROOT/hammerspoon/init.lua" "$HOME/.hammerspoon/init.lua" } setup_editor() { From 4ea159d4654b4ef9fc5fb3f63707a685ecfac61a Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 19:16:42 +0000 Subject: [PATCH 05/18] Improve ncmpcpp playlist columns --- ncmpcpp/config-macos | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ncmpcpp/config-macos b/ncmpcpp/config-macos index 3a09a58..e8961a9 100644 --- a/ncmpcpp/config-macos +++ b/ncmpcpp/config-macos @@ -9,7 +9,7 @@ playlist_display_mode = "columns" playlist_editor_display_mode = "columns" song_list_format = "{$8%a - $9}{$3%t$9}|{$8%f$9}" -song_columns_list_format = "(30)[white]{l} (45)[yellow]{a} (60)[cyan]{t} (40)[red]{b}" +song_columns_list_format = "(7f)[white]{lr:Time} (24)[yellow]{a:Artist} (36)[cyan]{t|f:Title} (33)[red]{b:Album}" media_library_primary_tag = "album_artist" display_bitrate = "yes" From 8f1bbd209bd89480d5f7f04c5d2b7639acdae6cd Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 20:01:42 +0000 Subject: [PATCH 06/18] Configure CodeCompanion Azure inline adapter --- nvim/init.lua | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/nvim/init.lua b/nvim/init.lua index 082e083..5e2d141 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -61,6 +61,38 @@ if not vim.uv.fs_stat(lazypath) then end vim.opt.rtp:prepend(lazypath) +local function azure_openai_v1_endpoint() + local endpoint = vim.env.AZURE_OPENAI_ENDPOINT + if endpoint and endpoint ~= "" then + endpoint = endpoint:gsub("/+$", "") + if not endpoint:match("/openai/v1$") then + endpoint = endpoint .. "/openai/v1" + end + return endpoint + end + + local ok, lines = pcall(vim.fn.readfile, vim.fn.expand("~/.codex/config.toml")) + if not ok then + return "" + end + + local in_azure_provider = false + for _, line in ipairs(lines) do + if line:match("^%s*%[model_providers%.azure%]%s*$") then + in_azure_provider = true + elseif in_azure_provider and line:match("^%s*%[") then + break + elseif in_azure_provider then + local base_url = line:match('^%s*base_url%s*=%s*"([^"]+)"') + if base_url then + return base_url:gsub("/+$", "") + end + end + end + + return "" +end + require("lazy").setup({ { "olimorris/codecompanion.nvim", @@ -70,6 +102,53 @@ require("lazy").setup({ }, opts = { adapters = { + http = { + azure_openai_gpt55 = function() + return require("codecompanion.adapters").extend("openai_responses", { + url = "${endpoint}/responses", + env = { + api_key = "AZURE_OPENAI_API_KEY", + endpoint = azure_openai_v1_endpoint, + }, + opts = { + compaction = false, + stream = false, + tools = false, + vision = true, + }, + schema = { + model = { + default = "gpt-5.5", + choices = { + ["gpt-5.5"] = { + formatted_name = "GPT-5.5", + meta = { context_window = 1050000 }, + opts = { + can_reason = true, + has_function_calling = true, + has_vision = true, + }, + }, + }, + }, + ["reasoning.effort"] = { + default = "medium", + }, + top_p = { + enabled = function() + return false + end, + }, + max_output_tokens = { + default = 2048, + }, + verbosity = { + default = "medium", + }, + }, + }) + end, + }, acp = { codex = function() return require("codecompanion.adapters").extend("codex", { @@ -91,8 +170,10 @@ require("lazy").setup({ chat = { adapter = "codex", }, + inline = { + adapter = "azure_openai_gpt55", + }, }, }, }, }) - From 238ad5197bbb5e32409caa3ca564e94401900159 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 20:08:37 +0000 Subject: [PATCH 07/18] Add CodeCompanion keymaps --- nvim/init.lua | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/nvim/init.lua b/nvim/init.lua index 5e2d141..be74b85 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -48,6 +48,28 @@ vim.keymap.set("n", "h", "nohlsearch", { silent = true }) vim.keymap.set("n", "", "bprevious", { silent = true }) vim.keymap.set("n", "", "bnext", { silent = true }) +vim.keymap.set({ "n", "v" }, "ca", "CodeCompanionActions", { + desc = "CodeCompanion actions", + silent = true, +}) +vim.keymap.set({ "n", "v" }, "cc", "CodeCompanionChat Toggle", { + desc = "Toggle CodeCompanion chat", + silent = true, +}) +vim.keymap.set("v", "cA", "CodeCompanionChat Add", { + desc = "Add selection to CodeCompanion chat", + silent = true, +}) +vim.keymap.set({ "n", "v" }, "ci", ":CodeCompanion ", { + desc = "CodeCompanion inline prompt", + silent = false, +}) +vim.keymap.set({ "n", "v" }, "cb", ":CodeCompanion #{buffer} ", { + desc = "CodeCompanion inline prompt with buffer", + silent = false, +}) +vim.cmd([[cab cc CodeCompanion]]) + local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim.uv.fs_stat(lazypath) then vim.fn.system({ @@ -172,6 +194,27 @@ require("lazy").setup({ }, inline = { adapter = "azure_openai_gpt55", + keymaps = { + stop = { + modes = { n = "cs" }, + }, + }, + }, + shared = { + keymaps = { + view_diff = { + modes = { n = "cd" }, + }, + accept_change = { + modes = { n = "cy" }, + }, + reject_change = { + modes = { n = "cn" }, + }, + always_accept = { + modes = { n = "cY" }, + }, + }, }, }, }, From 8db1b33971efc77a89f2108b438048f8d0e3d1ab Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 20:24:21 +0000 Subject: [PATCH 08/18] Use Neovim for git commit messages --- README.md | 8 ++++++++ nvim/init.lua | 21 +++++++++++++++++++++ setup.sh | 4 ++++ 3 files changed, 33 insertions(+) diff --git a/README.md b/README.md index 0b1dcca..31b3b3e 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,10 @@ Neovim is the primary editor target: With `--install-packages`, the editor profile installs `neovim` through Homebrew, plus `node` for `npx`-backed ACP adapters. +The editor profile also sets Git's global editor to `nvim` and enables verbose +commit templates, so `git commit` opens Neovim with the staged diff available as +commented context. + Vim gets a minimal fallback: - `vim/vimrc-basic` -> `~/.vimrc` @@ -101,6 +105,10 @@ may download plugins and the ACP adapter. ChatGPT auth must be available through Codex/Codex ACP on the machine; API-key and Azure OpenAI variants can be added later without changing the base music/editor/ranger setup. +In `git commit` buffers, press `,cm` to draft a commit message from the verbose +commit buffer. Press `,cy` to accept CodeCompanion's proposed edit or `,cn` to +reject it. + ## Ranger The ranger profile links the repo's ranger config into `~/.config/ranger/` and diff --git a/nvim/init.lua b/nvim/init.lua index be74b85..d000236 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -70,6 +70,27 @@ vim.keymap.set({ "n", "v" }, "cb", ":CodeCompanion #{buffer} ", { }) vim.cmd([[cab cc CodeCompanion]]) +vim.api.nvim_create_autocmd("FileType", { + pattern = "gitcommit", + callback = function(event) + vim.keymap.set("n", "cm", function() + local prompt = table.concat({ + "#{buffer}", + "Draft a concise git commit message from the staged diff and status in this buffer.", + "Insert it at the top above the commented lines.", + "Use imperative mood.", + "Include a short body only if it adds useful context.", + }, " ") + vim.cmd("normal! gg") + vim.cmd("CodeCompanion " .. prompt) + end, { + buffer = event.buf, + desc = "Draft git commit message", + silent = true, + }) + end, +}) + local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim.uv.fs_stat(lazypath) then vim.fn.system({ diff --git a/setup.sh b/setup.sh index 11ec574..0dd93ca 100755 --- a/setup.sh +++ b/setup.sh @@ -186,6 +186,10 @@ setup_editor() { fi link_path "$ROOT/nvim/init.lua" "$HOME/.config/nvim/init.lua" link_path "$ROOT/vim/vimrc-basic" "$HOME/.vimrc" + + run git config --global core.editor nvim + run git config --global commit.verbose true + run git config --global commit.status true } setup_ranger() { From c6e7169df0d3921255f9ffb268b6199478a909f9 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 22:18:09 +0000 Subject: [PATCH 09/18] Add GitUI setup profile --- README.md | 19 ++++++++++++++++++- setup.sh | 26 +++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 31b3b3e..6a46801 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,14 @@ small: - baseline Neovim config, plus a minimal Vim fallback - AI-enabled Neovim chat through CodeCompanion and Codex ACP - ranger config with portable clipboard helpers +- Git terminal tools such as `gitui` `ma08/botfiles` remains the home for coding-agent, orchestration, hooks, skills, -and automation bootstrap. This repo should own user-facing terminal app config. +secrets-aware shell bootstrap, and agent-specific aliases. This repo owns +general human terminal UX: shell ergonomics, editors, music, file navigation, +Git terminal tools, and user-facing app config. Botfiles can extend this +environment for agent workflows, but should not replace it as the general +dotfiles layer. ## Quick Start @@ -35,6 +40,12 @@ Install the macOS music packages and apply music config: ./setup.sh --music --install-packages --apply ``` +Install Git terminal tools: + +```bash +./setup.sh --git --install-packages --apply +``` + Install packages for the full v1 profile and apply all current config: ```bash @@ -116,6 +127,12 @@ installs `bin/dotfiles-clipboard-copy` into `~/.local/bin/`. The clipboard helpe uses `pbcopy`, `wl-copy`, `xclip`, or `xsel`, depending on what the machine has. With `--install-packages`, the ranger profile installs `ranger` through Homebrew. +## Git Tools + +The Git tools profile installs `gitui` through Homebrew/Linuxbrew. GitUI config +and keybindings are intentionally left as a TODO until the stock UI has been +used on both macOS and Linux. + ## Historical Material The old repo includes i3, polybar, Vimperator, X resources, old Linux desktop diff --git a/setup.sh b/setup.sh index 0dd93ca..1b068a4 100755 --- a/setup.sh +++ b/setup.sh @@ -7,10 +7,11 @@ INSTALL_PACKAGES=0 DO_MUSIC=0 DO_EDITOR=0 DO_RANGER=0 +DO_GIT=0 usage() { cat <<'USAGE' -Usage: ./setup.sh [--all] [--music] [--editor] [--ranger] [--install-packages] [--apply] +Usage: ./setup.sh [--all] [--music] [--editor] [--ranger] [--git] [--install-packages] [--apply] By default this is a dry run. Pass --apply to make changes. @@ -18,6 +19,7 @@ Profiles: --music Configure mpd/ncmpcpp --editor Configure Neovim and Vim fallback --ranger Configure ranger and clipboard helper + --git Configure Git terminal tools --all Configure all current v1 profiles Options: @@ -33,10 +35,12 @@ while [ "$#" -gt 0 ]; do DO_MUSIC=1 DO_EDITOR=1 DO_RANGER=1 + DO_GIT=1 ;; --music) DO_MUSIC=1 ;; --editor) DO_EDITOR=1 ;; --ranger) DO_RANGER=1 ;; + --git) DO_GIT=1 ;; --install-packages) INSTALL_PACKAGES=1 ;; --apply) APPLY=1 ;; -h|--help) @@ -52,7 +56,7 @@ while [ "$#" -gt 0 ]; do shift done -if [ "$DO_MUSIC$DO_EDITOR$DO_RANGER" = "000" ]; then +if [ "$DO_MUSIC$DO_EDITOR$DO_RANGER$DO_GIT" = "0000" ]; then usage >&2 exit 2 fi @@ -177,7 +181,12 @@ setup_music() { setup_editor() { log "== editor ==" if [ "$INSTALL_PACKAGES" -eq 1 ]; then - install_brew_packages neovim node + local packages=() + command -v nvim >/dev/null 2>&1 || packages+=(neovim) + command -v npx >/dev/null 2>&1 || packages+=(node) + if [ "${#packages[@]}" -gt 0 ]; then + install_brew_packages "${packages[@]}" + fi fi ensure_dir "$HOME/.local/state/nvim/undo" @@ -206,6 +215,16 @@ setup_ranger() { link_path "$ROOT/ranger/colorschemes" "$HOME/.config/ranger/colorschemes" } +setup_git_tools() { + log "== git tools ==" + if [ "$INSTALL_PACKAGES" -eq 1 ]; then + command -v gitui >/dev/null 2>&1 || install_brew_packages gitui + fi + + # GitUI config/keybindings are intentionally left for a later pass after + # using the stock UI on both macOS and Linux. +} + if [ "$APPLY" -eq 0 ]; then log "Dry run only. Re-run with --apply to make changes." fi @@ -213,5 +232,6 @@ fi [ "$DO_MUSIC" -eq 1 ] && setup_music [ "$DO_EDITOR" -eq 1 ] && setup_editor [ "$DO_RANGER" -eq 1 ] && setup_ranger +[ "$DO_GIT" -eq 1 ] && setup_git_tools log "Done." From 57dbb31987018cfa8453ff21c33883fd7ced9cbe Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Mon, 22 Jun 2026 22:44:04 +0000 Subject: [PATCH 10/18] Add GitUI vim keymap --- README.md | 5 +++-- gitui/key_bindings.ron | 35 +++++++++++++++++++++++++++++++++++ setup.sh | 3 +-- 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 gitui/key_bindings.ron diff --git a/README.md b/README.md index 6a46801..728f287 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,9 @@ With `--install-packages`, the ranger profile installs `ranger` through Homebrew ## Git Tools The Git tools profile installs `gitui` through Homebrew/Linuxbrew. GitUI config -and keybindings are intentionally left as a TODO until the stock UI has been -used on both macOS and Linux. +uses vim-style navigation through: + +- `gitui/key_bindings.ron` -> `~/.config/gitui/key_bindings.ron` ## Historical Material diff --git a/gitui/key_bindings.ron b/gitui/key_bindings.ron new file mode 100644 index 0000000..c7b1dbf --- /dev/null +++ b/gitui/key_bindings.ron @@ -0,0 +1,35 @@ +// Vim-style GitUI key bindings. +// +// GitUI reads this file from ~/.config/gitui/key_bindings.ron. +// Unspecified keys keep GitUI's built-in defaults. +( + open_help: Some(( code: F(1), modifiers: "")), + + move_left: Some(( code: Char('h'), modifiers: "")), + move_right: Some(( code: Char('l'), modifiers: "")), + move_up: Some(( code: Char('k'), modifiers: "")), + move_down: Some(( code: Char('j'), modifiers: "")), + + popup_up: Some(( code: Char('p'), modifiers: "CONTROL")), + popup_down: Some(( code: Char('n'), modifiers: "CONTROL")), + page_up: Some(( code: Char('b'), modifiers: "CONTROL")), + page_down: Some(( code: Char('f'), modifiers: "CONTROL")), + home: Some(( code: Char('g'), modifiers: "")), + end: Some(( code: Char('G'), modifiers: "SHIFT")), + shift_up: Some(( code: Char('K'), modifiers: "SHIFT")), + shift_down: Some(( code: Char('J'), modifiers: "SHIFT")), + + edit_file: Some(( code: Char('I'), modifiers: "SHIFT")), + + status_reset_item: Some(( code: Char('U'), modifiers: "SHIFT")), + + diff_reset_lines: Some(( code: Char('u'), modifiers: "")), + diff_stage_lines: Some(( code: Char('s'), modifiers: "")), + + stashing_save: Some(( code: Char('w'), modifiers: "")), + stashing_toggle_index: Some(( code: Char('m'), modifiers: "")), + + stash_open: Some(( code: Char('l'), modifiers: "")), + + abort_merge: Some(( code: Char('M'), modifiers: "SHIFT")), +) diff --git a/setup.sh b/setup.sh index 1b068a4..d0e4d98 100755 --- a/setup.sh +++ b/setup.sh @@ -221,8 +221,7 @@ setup_git_tools() { command -v gitui >/dev/null 2>&1 || install_brew_packages gitui fi - # GitUI config/keybindings are intentionally left for a later pass after - # using the stock UI on both macOS and Linux. + link_path "$ROOT/gitui/key_bindings.ron" "$HOME/.config/gitui/key_bindings.ron" } if [ "$APPLY" -eq 0 ]; then From abc3b89db9a0332324187767a4acc5688e952a38 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Tue, 23 Jun 2026 00:24:46 +0000 Subject: [PATCH 11/18] Show CodeCompanion request status --- README.md | 4 ++ nvim/init.lua | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/README.md b/README.md index 728f287..8825979 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,10 @@ In `git commit` buffers, press `,cm` to draft a commit message from the verbose commit buffer. Press `,cy` to accept CodeCompanion's proposed edit or `,cn` to reject it. +While CodeCompanion is waiting on a chat or inline request, the statusline shows +an `AI` spinner with the active adapter/model. It briefly changes to `AI done`, +`AI cancelled`, or `AI error` when the request finishes. + ## Ranger The ranger profile links the repo's ranger config into `~/.config/ranger/` and diff --git a/nvim/init.lua b/nvim/init.lua index d000236..f1fd12d 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -30,6 +30,7 @@ opt.wildmenu = true opt.wildmode = { "longest:full", "full" } opt.timeoutlen = 400 opt.updatetime = 300 +opt.laststatus = 2 opt.undofile = true opt.undodir = vim.fn.expand("~/.local/state/nvim/undo//") @@ -48,6 +49,154 @@ vim.keymap.set("n", "h", "nohlsearch", { silent = true }) vim.keymap.set("n", "", "bprevious", { silent = true }) vim.keymap.set("n", "", "bnext", { silent = true }) +local codecompanion_status = { + active = 0, + clear_timer = nil, + frames = { "|", "/", "-", "\\" }, + message = "", + spinner = 1, + timer = nil, +} + +local function codecompanion_request_label(data) + local adapter = data and data.adapter or {} + local name = adapter.formatted_name or adapter.name or "AI" + local model = adapter.model + + if model and model ~= "" then + return name .. " " .. model + end + + return name +end + +local function codecompanion_redraw_status() + pcall(vim.cmd, "redrawstatus") +end + +local function codecompanion_clear_later() + if codecompanion_status.clear_timer then + codecompanion_status.clear_timer:stop() + codecompanion_status.clear_timer:close() + end + + codecompanion_status.clear_timer = vim.uv.new_timer() + codecompanion_status.clear_timer:start( + 2500, + 0, + vim.schedule_wrap(function() + codecompanion_status.message = "" + codecompanion_redraw_status() + end) + ) +end + +local function codecompanion_start_spinner() + if codecompanion_status.clear_timer then + codecompanion_status.clear_timer:stop() + codecompanion_status.clear_timer:close() + codecompanion_status.clear_timer = nil + end + + if codecompanion_status.timer then + return + end + + codecompanion_status.timer = vim.uv.new_timer() + codecompanion_status.timer:start( + 0, + 120, + vim.schedule_wrap(function() + if codecompanion_status.active <= 0 then + return + end + + codecompanion_status.spinner = (codecompanion_status.spinner % #codecompanion_status.frames) + 1 + codecompanion_redraw_status() + end) + ) +end + +local function codecompanion_stop_spinner() + if codecompanion_status.timer then + codecompanion_status.timer:stop() + codecompanion_status.timer:close() + codecompanion_status.timer = nil + end + codecompanion_redraw_status() +end + +function _G.CodeCompanionStatus() + if codecompanion_status.active > 0 then + return string.format( + " AI %s %s", + codecompanion_status.frames[codecompanion_status.spinner], + codecompanion_status.message + ) + end + + if codecompanion_status.message ~= "" then + return " AI " .. codecompanion_status.message + end + + return "" +end + +opt.statusline = table.concat({ + "%f", + "%m%r%h%w", + "%=", + "%{v:lua.CodeCompanionStatus()}", + " %y", + " %l:%c", + " %P", +}) + +vim.api.nvim_create_autocmd("User", { + pattern = { + "CodeCompanionRequestStarted", + "CodeCompanionRequestStreaming", + "CodeCompanionRequestFinished", + }, + group = vim.api.nvim_create_augroup("DotfilesCodeCompanionStatus", { clear = true }), + callback = function(event) + local data = event.data or {} + + if event.match == "CodeCompanionRequestStarted" then + codecompanion_status.active = codecompanion_status.active + 1 + codecompanion_status.message = codecompanion_request_label(data) + codecompanion_start_spinner() + codecompanion_redraw_status() + return + end + + if event.match == "CodeCompanionRequestStreaming" then + codecompanion_status.message = "streaming " .. codecompanion_request_label(data) + codecompanion_redraw_status() + return + end + + codecompanion_status.active = math.max(0, codecompanion_status.active - 1) + local status = data.status or "success" + + if status == "success" then + codecompanion_status.message = "done" + elseif status == "cancelled" or status == "canceled" then + codecompanion_status.message = "cancelled" + else + codecompanion_status.message = "error" + vim.notify("CodeCompanion request failed: " .. tostring(status), vim.log.levels.WARN) + end + + if codecompanion_status.active == 0 then + codecompanion_stop_spinner() + codecompanion_clear_later() + else + codecompanion_redraw_status() + end + end, +}) + vim.keymap.set({ "n", "v" }, "ca", "CodeCompanionActions", { desc = "CodeCompanion actions", silent = true, From fade7454ae1c3504d40df860b7df5fb7fef11f3c Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Tue, 23 Jun 2026 02:38:29 +0000 Subject: [PATCH 12/18] Avoid GitUI directory command collision --- README.md | 6 +++++- bin/dotfiles-gitui | 18 ++++++++++++++++++ {gitui => config/gitui}/key_bindings.ron | 0 setup.sh | 6 +++++- 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100755 bin/dotfiles-gitui rename {gitui => config/gitui}/key_bindings.ron (100%) diff --git a/README.md b/README.md index 8825979..c06b269 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,11 @@ With `--install-packages`, the ranger profile installs `ranger` through Homebrew The Git tools profile installs `gitui` through Homebrew/Linuxbrew. GitUI config uses vim-style navigation through: -- `gitui/key_bindings.ron` -> `~/.config/gitui/key_bindings.ron` +- `config/gitui/key_bindings.ron` -> `~/.config/gitui/key_bindings.ron` + +The setup also links `bin/dotfiles-gitui` to `~/.local/bin/gitui` on machines +where GitUI is installed by Homebrew/Linuxbrew. Keeping config under +`config/gitui/` avoids a zsh `AUTO_CD` collision with the `gitui` command. ## Historical Material diff --git a/bin/dotfiles-gitui b/bin/dotfiles-gitui new file mode 100755 index 0000000..65cb46d --- /dev/null +++ b/bin/dotfiles-gitui @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ -x /opt/homebrew/bin/gitui ]; then + exec /opt/homebrew/bin/gitui "$@" +fi + +if [ -x /home/linuxbrew/.linuxbrew/bin/gitui ]; then + exec /home/linuxbrew/.linuxbrew/bin/gitui "$@" +fi + +resolved="$(command -v gitui 2>/dev/null || true)" +if [ -n "$resolved" ] && [ "$resolved" != "$0" ]; then + exec "$resolved" "$@" +fi + +echo "ERROR: gitui binary not found; install GitUI or rerun setup with --install-packages" >&2 +exit 127 diff --git a/gitui/key_bindings.ron b/config/gitui/key_bindings.ron similarity index 100% rename from gitui/key_bindings.ron rename to config/gitui/key_bindings.ron diff --git a/setup.sh b/setup.sh index d0e4d98..f629bf3 100755 --- a/setup.sh +++ b/setup.sh @@ -221,7 +221,11 @@ setup_git_tools() { command -v gitui >/dev/null 2>&1 || install_brew_packages gitui fi - link_path "$ROOT/gitui/key_bindings.ron" "$HOME/.config/gitui/key_bindings.ron" + if [ -x /opt/homebrew/bin/gitui ] || [ -x /home/linuxbrew/.linuxbrew/bin/gitui ]; then + link_path "$ROOT/bin/dotfiles-gitui" "$HOME/.local/bin/gitui" + fi + + link_path "$ROOT/config/gitui/key_bindings.ron" "$HOME/.config/gitui/key_bindings.ron" } if [ "$APPLY" -eq 0 ]; then From 808635de57583041c3c20fe6f00c62d9dd003ab5 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Wed, 24 Jun 2026 03:58:30 +0000 Subject: [PATCH 13/18] Remove trailing blank lines --- bin/dotfiles-clipboard-copy | 1 - vim/vimrc-basic | 1 - 2 files changed, 2 deletions(-) diff --git a/bin/dotfiles-clipboard-copy b/bin/dotfiles-clipboard-copy index af39c82..d69768b 100755 --- a/bin/dotfiles-clipboard-copy +++ b/bin/dotfiles-clipboard-copy @@ -20,4 +20,3 @@ fi cat >/dev/null echo "dotfiles-clipboard-copy: no clipboard command found" >&2 exit 1 - diff --git a/vim/vimrc-basic b/vim/vimrc-basic index d795c41..869e17c 100644 --- a/vim/vimrc-basic +++ b/vim/vimrc-basic @@ -34,4 +34,3 @@ inoremap jk vnoremap < >gv nnoremap h :nohlsearch - From 68658c62f7788edad00095f90645871a5fcd94ad Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Thu, 2 Jul 2026 04:29:25 +0000 Subject: [PATCH 14/18] Restart MPD after Mac audio device changes --- README.md | 5 +++ bin/dotfiles-mpd-restart | 96 ++++++++++++++++++++++++++++++++++++++++ hammerspoon/init.lua | 36 +++++++++++++++ setup.sh | 1 + 4 files changed, 138 insertions(+) create mode 100755 bin/dotfiles-mpd-restart diff --git a/README.md b/README.md index c06b269..410067a 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Relevant paths: - MPD music directory: `/Users/sourya4/pro/youtube_download/music` - ncmpcpp config: `~/.ncmpcpp/config` - ncmpcpp bindings: `~/.ncmpcpp/bindings` +- MPD CoreAudio restart helper: `~/.local/bin/dotfiles-mpd-restart` Basic checks: @@ -78,6 +79,10 @@ mpc update ncmpcpp ``` +Hammerspoon watches for macOS audio output changes and runs the MPD restart +helper when MPD is already running. This works around stale CoreAudio device +handles after plugging, unplugging, or switching output devices. + Terminal playback is the v1 success gate. macOS media keys / Now Playing support is a follow-up unless it becomes simple and reliable. diff --git a/bin/dotfiles-mpd-restart b/bin/dotfiles-mpd-restart new file mode 100755 index 0000000..cd02beb --- /dev/null +++ b/bin/dotfiles-mpd-restart @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -euo pipefail + +only_if_running=0 +reason="manual" + +while [ "$#" -gt 0 ]; do + case "$1" in + --if-running) + only_if_running=1 + ;; + --reason) + shift + reason="${1:-manual}" + ;; + *) + echo "ERROR: unknown argument: $1" >&2 + exit 2 + ;; + esac + shift +done + +mpd_bin="${MPD_BIN:-}" +mpc_bin="${MPC_BIN:-}" +mpd_config="${MPD_CONFIG:-$HOME/.mpd/mpd.conf}" +mpd_log_dir="$HOME/.mpd" +restart_log="$mpd_log_dir/restart.log" + +if [ -z "$mpd_bin" ]; then + if [ -x /opt/homebrew/bin/mpd ]; then + mpd_bin=/opt/homebrew/bin/mpd + elif [ -x /home/linuxbrew/.linuxbrew/bin/mpd ]; then + mpd_bin=/home/linuxbrew/.linuxbrew/bin/mpd + else + mpd_bin="$(command -v mpd 2>/dev/null || true)" + fi +fi + +if [ -z "$mpc_bin" ]; then + if [ -x /opt/homebrew/bin/mpc ]; then + mpc_bin=/opt/homebrew/bin/mpc + elif [ -x /home/linuxbrew/.linuxbrew/bin/mpc ]; then + mpc_bin=/home/linuxbrew/.linuxbrew/bin/mpc + else + mpc_bin="$(command -v mpc 2>/dev/null || true)" + fi +fi + +if [ -z "$mpd_bin" ] || [ -z "$mpc_bin" ]; then + echo "ERROR: mpd/mpc not found" >&2 + exit 127 +fi + +mkdir -p "$mpd_log_dir" + +log() { + printf '%s %s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "$*" >> "$restart_log" +} + +if ! pgrep -x mpd >/dev/null 2>&1; then + if [ "$only_if_running" -eq 1 ]; then + log "skip: mpd not running reason=$reason" + exit 0 + fi +fi + +status="$("$mpc_bin" -h 127.0.0.1 -p 6600 status 2>/dev/null || true)" +was_playing=0 +if printf '%s\n' "$status" | grep -q '\[playing\]'; then + was_playing=1 +fi + +log "restart begin reason=$reason was_playing=$was_playing" + +pkill -x mpd >/dev/null 2>&1 || true +for _ in 1 2 3 4 5; do + if ! pgrep -x mpd >/dev/null 2>&1; then + break + fi + sleep 0.2 +done + +if pgrep -x mpd >/dev/null 2>&1; then + pkill -KILL -x mpd >/dev/null 2>&1 || true + sleep 0.5 +fi + +"$mpd_bin" "$mpd_config" +sleep 0.8 + +if [ "$was_playing" -eq 1 ]; then + "$mpc_bin" -h 127.0.0.1 -p 6600 play >/dev/null 2>&1 || true +fi + +log "restart complete reason=$reason" diff --git a/hammerspoon/init.lua b/hammerspoon/init.lua index 2c226a3..0e16ef8 100644 --- a/hammerspoon/init.lua +++ b/hammerspoon/init.lua @@ -1,4 +1,5 @@ local mpc = "/opt/homebrew/bin/mpc" +local mpdRestart = os.getenv("HOME") .. "/.local/bin/dotfiles-mpd-restart" local mpdHost = "127.0.0.1" local mpdPort = "6600" @@ -21,6 +22,17 @@ local function runMpc(args) hs.execute(mpcCommand(args) .. " >/dev/null 2>&1", true) end +local function runMpdRestart(reason) + local command = table.concat({ + shellQuote(mpdRestart), + "--if-running", + "--reason", + shellQuote(reason), + ">/dev/null 2>&1", + }, " ") + hs.execute(command, true) +end + local function mpdHasQueue() local output, ok = hs.execute(mpcCommand("playlist"), true) return ok and output ~= nil and output:match("%S") ~= nil @@ -66,3 +78,27 @@ if hs.accessibilityState(true) then else hs.alert.show("Enable Accessibility for Hammerspoon to control MPD media keys") end + +local mpdAudioRestartTimer = nil + +local function scheduleMpdAudioRestart(eventName) + if eventName ~= "dOut" and eventName ~= "sOut" and eventName ~= "dev#" then + return + end + + if mpdAudioRestartTimer ~= nil then + mpdAudioRestartTimer:stop() + end + + mpdAudioRestartTimer = hs.timer.doAfter(2.0, function() + local device = hs.audiodevice.defaultOutputDevice() + local deviceName = "unknown-output" + if device ~= nil then + deviceName = device:name() or deviceName + end + runMpdRestart("audio-device-" .. eventName .. "-" .. deviceName) + end) +end + +hs.audiodevice.watcher.setCallback(scheduleMpdAudioRestart) +hs.audiodevice.watcher.start() diff --git a/setup.sh b/setup.sh index f629bf3..3eb756b 100755 --- a/setup.sh +++ b/setup.sh @@ -172,6 +172,7 @@ setup_music() { touch_file "$HOME/.mpd/sticker.sql" ensure_dir "$HOME/.ncmpcpp" + link_path "$ROOT/bin/dotfiles-mpd-restart" "$HOME/.local/bin/dotfiles-mpd-restart" link_path "$ROOT/mpd/mpd-macos.conf" "$HOME/.mpd/mpd.conf" link_path "$ROOT/ncmpcpp/config-macos" "$HOME/.ncmpcpp/config" link_path "$ROOT/ncmpcpp/bindings" "$HOME/.ncmpcpp/bindings" From e7ce0ee4a59b18aeb66330668969789d652f26a5 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Thu, 2 Jul 2026 04:33:59 +0000 Subject: [PATCH 15/18] Route MPD media keys more carefully --- README.md | 5 ++++ hammerspoon/init.lua | 62 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 410067a..e4ad4cd 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,11 @@ Hammerspoon watches for macOS audio output changes and runs the MPD restart helper when MPD is already running. This works around stale CoreAudio device handles after plugging, unplugging, or switching output devices. +Hammerspoon also maps macOS media keys to MPD for play/pause, next, and +previous. To avoid stealing media keys from Chrome or other players, it only +handles them when MPD is already playing, or when MPD is paused and a terminal +app is frontmost. + Terminal playback is the v1 success gate. macOS media keys / Now Playing support is a follow-up unless it becomes simple and reliable. diff --git a/hammerspoon/init.lua b/hammerspoon/init.lua index 0e16ef8..7f70129 100644 --- a/hammerspoon/init.lua +++ b/hammerspoon/init.lua @@ -33,15 +33,69 @@ local function runMpdRestart(reason) hs.execute(command, true) end -local function mpdHasQueue() - local output, ok = hs.execute(mpcCommand("playlist"), true) - return ok and output ~= nil and output:match("%S") ~= nil +local mpdTerminalApps = { + ["Alacritty"] = true, + ["Ghostty"] = true, + ["iTerm2"] = true, + ["kitty"] = true, + ["Terminal"] = true, + ["WezTerm"] = true, + ["Warp"] = true, +} + +local function mpdPlaybackState() + local output, ok = hs.execute(mpcCommand("status"), true) + if not ok or output == nil then + return nil + end + + if output:match("%[playing%]") then + return "playing" + end + + if output:match("%[paused%]") then + return "paused" + end + + return nil +end + +local function frontmostAppName() + local app = hs.application.frontmostApplication() + if app == nil then + return "" + end + + return app:name() or "" +end + +local function shouldHandleMpdMediaKey() + local state = mpdPlaybackState() + if state == "playing" then + return true + end + + if state == "paused" and mpdTerminalApps[frontmostAppName()] then + return true + end + + return false +end + +function mpdMediaKeyStatus() + return { + frontmost = frontmostAppName(), + handles = shouldHandleMpdMediaKey(), + state = mpdPlaybackState(), + } end local mediaActions = { + FAST = "next", PLAY = "toggle", NEXT = "next", PREVIOUS = "prev", + REWIND = "prev", } local ipc = require("hs.ipc") @@ -58,7 +112,7 @@ local function startMpdMediaKeys() end local action = mediaActions[systemKey.key] - if action == nil or not mpdHasQueue() then + if action == nil or not shouldHandleMpdMediaKey() then return false end From d7d05e5aa2e1962c11dfb34ceefa99c73927c9d9 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Thu, 2 Jul 2026 04:37:22 +0000 Subject: [PATCH 16/18] Let play resume paused MPD outside terminal --- README.md | 5 +++-- hammerspoon/init.lua | 32 ++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e4ad4cd..be47691 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,9 @@ handles after plugging, unplugging, or switching output devices. Hammerspoon also maps macOS media keys to MPD for play/pause, next, and previous. To avoid stealing media keys from Chrome or other players, it only -handles them when MPD is already playing, or when MPD is paused and a terminal -app is frontmost. +handles them when MPD is already playing, or when MPD is paused and the +frontmost app is not a known media app such as Chrome, Safari, Spotify, Music, +VLC, or IINA. Terminal playback is the v1 success gate. macOS media keys / Now Playing support is a follow-up unless it becomes simple and reliable. diff --git a/hammerspoon/init.lua b/hammerspoon/init.lua index 7f70129..9c0a327 100644 --- a/hammerspoon/init.lua +++ b/hammerspoon/init.lua @@ -33,14 +33,20 @@ local function runMpdRestart(reason) hs.execute(command, true) end -local mpdTerminalApps = { - ["Alacritty"] = true, - ["Ghostty"] = true, - ["iTerm2"] = true, - ["kitty"] = true, - ["Terminal"] = true, - ["WezTerm"] = true, - ["Warp"] = true, +local mpdMediaPassThroughApps = { + ["Arc"] = true, + ["Brave Browser"] = true, + ["Firefox"] = true, + ["Google Chrome"] = true, + ["IINA"] = true, + ["Music"] = true, + ["Podcasts"] = true, + ["QuickTime Player"] = true, + ["Safari"] = true, + ["Spotify"] = true, + ["TV"] = true, + ["VLC"] = true, + ["YouTube Music"] = true, } local function mpdPlaybackState() @@ -75,17 +81,19 @@ local function shouldHandleMpdMediaKey() return true end - if state == "paused" and mpdTerminalApps[frontmostAppName()] then - return true + if state ~= "paused" then + return false end - return false + return not mpdMediaPassThroughApps[frontmostAppName()] end function mpdMediaKeyStatus() + local frontmost = frontmostAppName() return { - frontmost = frontmostAppName(), + frontmost = frontmost, handles = shouldHandleMpdMediaKey(), + passThrough = mpdMediaPassThroughApps[frontmost] == true, state = mpdPlaybackState(), } end From 68c9b65ac74b75b92b52b91023f8694e5c8daad8 Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Sun, 12 Jul 2026 03:35:17 +0000 Subject: [PATCH 17/18] Remap ncmpcpp horizontal keys to navigation --- ncmpcpp/bindings | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/ncmpcpp/bindings b/ncmpcpp/bindings index e42a442..1c01d27 100644 --- a/ncmpcpp/bindings +++ b/ncmpcpp/bindings @@ -3,12 +3,28 @@ def_key "j" def_key "k" scroll_up def_key "h" - seek_backward + previous_column +def_key "h" + master_screen def_key "l" - seek_forward + next_column +def_key "l" + slave_screen def_key "L" - next + next_column +def_key "L" + slave_screen +def_key "H" + previous_column def_key "H" + master_screen +def_key "b" + seek_backward +def_key "f" + seek_forward +def_key ">" + next +def_key "<" previous def_key "K" scroll_up_album From 4854596d8497eb351cd29b9eb94b23bbcf290c8c Mon Sep 17 00:00:00 2001 From: Sourya Kakarla Date: Sat, 25 Jul 2026 00:08:02 +0000 Subject: [PATCH 18/18] Use Gemini Flash for inline CodeCompanion edits --- README.md | 9 +++++-- nvim/init.lua | 68 ++++++++------------------------------------------- 2 files changed, 17 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index be47691..e570711 100644 --- a/README.md +++ b/README.md @@ -124,8 +124,13 @@ npx -y @zed-industries/codex-acp Use `:CodeCompanionChat` inside Neovim to open an AI chat buffer. The first run may download plugins and the ACP adapter. ChatGPT auth must be available through -Codex/Codex ACP on the machine; API-key and Azure OpenAI variants can be added -later without changing the base music/editor/ranger setup. +Codex/Codex ACP on the machine. + +Inline edits use Gemini 3.6 Flash through CodeCompanion's Gemini HTTP adapter. +Set `GEMINI_API_KEY` in the machine-local botfiles secrets loaded by `.botenv`; +do not place it in this repository. The adapter is intentionally non-streaming +because CodeCompanion's Gemini inline handler requires a complete response. It +uses Gemini's `minimal` thinking level to keep inline edits responsive. In `git commit` buffers, press `,cm` to draft a commit message from the verbose commit buffer. Press `,cy` to accept CodeCompanion's proposed edit or `,cn` to diff --git a/nvim/init.lua b/nvim/init.lua index f1fd12d..85959f7 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -253,38 +253,6 @@ if not vim.uv.fs_stat(lazypath) then end vim.opt.rtp:prepend(lazypath) -local function azure_openai_v1_endpoint() - local endpoint = vim.env.AZURE_OPENAI_ENDPOINT - if endpoint and endpoint ~= "" then - endpoint = endpoint:gsub("/+$", "") - if not endpoint:match("/openai/v1$") then - endpoint = endpoint .. "/openai/v1" - end - return endpoint - end - - local ok, lines = pcall(vim.fn.readfile, vim.fn.expand("~/.codex/config.toml")) - if not ok then - return "" - end - - local in_azure_provider = false - for _, line in ipairs(lines) do - if line:match("^%s*%[model_providers%.azure%]%s*$") then - in_azure_provider = true - elseif in_azure_provider and line:match("^%s*%[") then - break - elseif in_azure_provider then - local base_url = line:match('^%s*base_url%s*=%s*"([^"]+)"') - if base_url then - return base_url:gsub("/+$", "") - end - end - end - - return "" -end - require("lazy").setup({ { "olimorris/codecompanion.nvim", @@ -295,47 +263,31 @@ require("lazy").setup({ opts = { adapters = { http = { - azure_openai_gpt55 = function() - return require("codecompanion.adapters").extend("openai_responses", { - url = "${endpoint}/responses", - env = { - api_key = "AZURE_OPENAI_API_KEY", - endpoint = azure_openai_v1_endpoint, - }, + gemini_36_flash = function() + return require("codecompanion.adapters").extend("gemini", { opts = { - compaction = false, stream = false, tools = false, vision = true, }, schema = { model = { - default = "gpt-5.5", + default = "gemini-3.6-flash", choices = { - ["gpt-5.5"] = { - formatted_name = "GPT-5.5", + ["gemini-3.6-flash"] = { + formatted_name = "Gemini 3.6 Flash", meta = { context_window = 1050000 }, opts = { can_reason = true, - has_function_calling = true, + can_form_structured_outputs = true, has_vision = true, }, }, }, }, - ["reasoning.effort"] = { - default = "medium", - }, - top_p = { - enabled = function() - return false - end, - }, - max_output_tokens = { - default = 2048, - }, - verbosity = { - default = "medium", + thinkingLevel = { + default = "minimal", + choices = { "minimal", "low", "medium", "high" }, }, }, }) @@ -363,7 +315,7 @@ require("lazy").setup({ adapter = "codex", }, inline = { - adapter = "azure_openai_gpt55", + adapter = "gemini_36_flash", keymaps = { stop = { modes = { n = "cs" },