theme and scripts

This commit is contained in:
Harivansh Rathi 2026-03-20 14:22:40 -04:00
parent deade2bafb
commit 7ae7c1ceec
17 changed files with 575 additions and 270 deletions

View file

@ -1,74 +1,49 @@
return { return {
{ {
'harivansh-afk/cozybox.nvim', dir = vim.fn.expand "~/Documents/GitHub/cozybox.nvim",
lazy = false, name = "cozybox.nvim",
priority = 1000, lazy = false,
config = function() priority = 1000,
local function apply_cozybox_overrides() config = function() require("theme").setup() end,
local links = { },
{ 'DiffsAdd', 'DiffAdd' }, {
{ 'DiffsDelete', 'DiffDelete' }, "nvim-lualine/lualine.nvim",
{ 'DiffsChange', 'DiffChange' }, dependencies = { "nvim-tree/nvim-web-devicons" },
{ 'DiffsText', 'DiffText' }, config = function()
{ 'DiffsClear', 'Normal' }, local theme = {
} normal = {
for _, pair in ipairs(links) do a = { gui = "bold" },
vim.api.nvim_set_hl(0, pair[1], { link = pair[2], default = false }) },
end visual = {
end a = { gui = "bold" },
},
vim.api.nvim_create_augroup('cozybox_fallback_highlights', { clear = true }) replace = {
vim.api.nvim_create_autocmd('ColorScheme', { a = { gui = "bold" },
group = 'cozybox_fallback_highlights', },
callback = function() command = {
if vim.g.colors_name == 'cozybox' then a = { gui = "bold" },
apply_cozybox_overrides() },
end }
end, require("lualine").setup {
}) options = {
icons_enabled = false,
vim.cmd.colorscheme('cozybox') component_separators = "",
apply_cozybox_overrides() section_separators = { left = "", right = "" },
end, theme = theme,
}, },
{ sections = {
'nvim-lualine/lualine.nvim', lualine_a = { "mode" },
dependencies = { 'nvim-tree/nvim-web-devicons' }, lualine_b = { "FugitiveHead", "diff" },
config = function() lualine_c = { { "filename", path = 0 } },
local theme = { lualine_x = { "diagnostics" },
normal = { lualine_y = { "filetype" },
a = { gui = 'bold' }, lualine_z = { "progress" },
}, },
visual = { }
a = { gui = 'bold' }, end,
}, },
replace = { {
a = { gui = 'bold' }, "barrettruth/nonicons.nvim",
}, dependencies = { "nvim-tree/nvim-web-devicons" },
command = { },
a = { gui = 'bold' },
},
}
require('lualine').setup({
options = {
icons_enabled = false,
component_separators = '',
section_separators = { left = '', right = '' },
theme = theme,
},
sections = {
lualine_a = { 'mode' },
lualine_b = { 'FugitiveHead', 'diff' },
lualine_c = { { 'filename', path = 0 } },
lualine_x = { 'diagnostics' },
lualine_y = { 'filetype' },
lualine_z = { 'progress' },
},
})
end,
},
{
'barrettruth/nonicons.nvim',
dependencies = { 'nvim-tree/nvim-web-devicons' },
},
} }

88
config/nvim/lua/theme.lua Normal file
View file

@ -0,0 +1,88 @@
local M = {}
local theme_state_file = vim.fn.stdpath "state" .. "/theme/current"
local active_schemes = {
cozybox = true,
["cozybox-light"] = true,
}
local function ensure_server_socket()
if vim.v.servername ~= nil and vim.v.servername ~= "" then
return
end
local socket_path = ("/tmp/nvim-%d.sock"):format(vim.fn.getpid())
vim.fn.serverstart(socket_path)
end
local function apply_cozybox_overrides()
local links = {
{ "DiffsAdd", "DiffAdd" },
{ "DiffsDelete", "DiffDelete" },
{ "DiffsChange", "DiffChange" },
{ "DiffsText", "DiffText" },
{ "DiffsClear", "Normal" },
}
for _, pair in ipairs(links) do
vim.api.nvim_set_hl(0, pair[1], { link = pair[2], default = false })
end
end
local function read_mode()
local ok, lines = pcall(vim.fn.readfile, theme_state_file)
if not ok or not lines or not lines[1] then return "dark" end
local mode = vim.trim(lines[1])
if mode == "light" then return "light" end
return "dark"
end
local function colorscheme_for_mode(mode)
if mode == "light" then return "cozybox-light" end
return "cozybox"
end
function M.apply(mode)
local next_mode = mode or read_mode()
local next_scheme = colorscheme_for_mode(next_mode)
if vim.o.background ~= next_mode then vim.o.background = next_mode end
if vim.g.cozybox_theme_mode ~= next_mode or vim.g.colors_name ~= next_scheme then vim.cmd.colorscheme(next_scheme) end
vim.g.cozybox_theme_mode = next_mode
apply_cozybox_overrides()
end
function M.setup()
local group = vim.api.nvim_create_augroup("cozybox_theme_sync", { clear = true })
ensure_server_socket()
vim.api.nvim_create_autocmd("ColorScheme", {
group = group,
callback = function()
if active_schemes[vim.g.colors_name] then apply_cozybox_overrides() end
end,
})
vim.api.nvim_create_autocmd({ "VimEnter", "FocusGained" }, {
group = group,
callback = function() M.apply() end,
})
vim.api.nvim_create_user_command("ThemeSync", function(opts)
local mode = opts.args ~= "" and opts.args or nil
M.apply(mode)
end, {
nargs = "?",
complete = function() return { "dark", "light" } end,
})
M.apply()
end
return M

View file

@ -13,6 +13,7 @@
./migration.nix ./migration.nix
./nvim.nix ./nvim.nix
./rectangle.nix ./rectangle.nix
./scripts.nix
./tmux.nix ./tmux.nix
./zsh.nix ./zsh.nix
]; ];

View file

@ -1,11 +1,11 @@
{ pkgs, ... }: {
let config,
pkgs,
...
}: let
theme = import ../lib/theme.nix {inherit config;};
ghosttyConfig = '' ghosttyConfig = ''
theme = "Gruvbox Material Dark" theme = "dark:cozybox-dark,light:cozybox-light"
background = #181818
cursor-color = #ddc7a1
selection-background = #504945
selection-foreground = #ebdbb2
font-family = Berkeley Mono font-family = Berkeley Mono
font-codepoint-map = U+f101-U+f25c=nonicons font-codepoint-map = U+f101-U+f25c=nonicons
background-opacity = 1 background-opacity = 1
@ -38,12 +38,6 @@ let
keybind = vim/i=deactivate_key_table keybind = vim/i=deactivate_key_table
keybind = vim/catch_all=ignore keybind = vim/catch_all=ignore
mouse-hide-while-typing = true mouse-hide-while-typing = true
palette = 2=#8ec97c
palette = 10=#8ec97c
palette = 4=#4672d4
palette = 12=#4672d4
palette = 6=#8ec07c
palette = 14=#8ec07c
macos-titlebar-style = hidden macos-titlebar-style = hidden
macos-option-as-alt = true macos-option-as-alt = true
confirm-close-surface = true confirm-close-surface = true
@ -67,6 +61,9 @@ in {
force = true; force = true;
}; };
xdg.configFile."ghostty/themes/cozybox-dark".text = theme.renderGhostty "dark";
xdg.configFile."ghostty/themes/cozybox-light".text = theme.renderGhostty "light";
home.file."Library/Application Support/com.mitchellh.ghostty/config.ghostty" = { home.file."Library/Application Support/com.mitchellh.ghostty/config.ghostty" = {
text = ghosttyConfig; text = ghosttyConfig;
force = true; force = true;

33
home/scripts.nix Normal file
View file

@ -0,0 +1,33 @@
{
config,
lib,
pkgs,
...
}: let
customScripts = import ../scripts {inherit config lib pkgs;};
in {
home.packages = builtins.attrValues customScripts.packages;
home.activation.initializeThemeState = lib.hm.dag.entryAfter ["writeBoundary"] ''
mkdir -p "${customScripts.theme.paths.stateDir}" "${customScripts.theme.paths.tmuxDir}"
if [[ -f "${customScripts.theme.paths.stateFile}" ]]; then
mode=$(tr -d '[:space:]' < "${customScripts.theme.paths.stateFile}")
else
mode="${customScripts.theme.defaultMode}"
printf '%s\n' "$mode" > "${customScripts.theme.paths.stateFile}"
fi
case "$mode" in
light)
tmux_target="${customScripts.tmuxConfigs.light}"
;;
*)
printf '%s\n' "${customScripts.theme.defaultMode}" > "${customScripts.theme.paths.stateFile}"
tmux_target="${customScripts.tmuxConfigs.dark}"
;;
esac
ln -sfn "$tmux_target" "${customScripts.theme.paths.tmuxCurrentFile}"
'';
}

View file

@ -1,4 +1,11 @@
{lib, pkgs, ...}: { {
config,
lib,
pkgs,
...
}: let
theme = import ../lib/theme.nix {inherit config;};
in {
programs.tmux = { programs.tmux = {
enable = true; enable = true;
plugins = with pkgs.tmuxPlugins; [ plugins = with pkgs.tmuxPlugins; [
@ -75,54 +82,14 @@
# Disable waiting time when pressing escape, for smoother Neovim usage. # Disable waiting time when pressing escape, for smoother Neovim usage.
set-option -s escape-time 0 set-option -s escape-time 0
# Styling set-option -g prompt-cursor-colour default
RED="#ea6962"
GREEN="#8ec97c"
YELLOW="#d8a657"
BLUE="#4672d4"
MAGENTA="#d3869b"
CYAN="#89b482"
BLACK="#1d2021"
DARK_GRAY="#181818"
LIGHT_GRAY="#4F4946"
# Match Ghostty theme (cozybox-override palette + Gruvbox Material Dark base)
BG="#181818"
FG="#d4be98"
HALF_ROUND_OPEN="#(printf '\uE0B2')"
HALF_ROUND_CLOSE="#(printf '\uE0B0')"
TRIANGLE_OPEN="#(printf '\uE0B2')"
TRIANGLE_CLOSE="#(printf '\uE0B0')"
set-option -g status-position bottom set-option -g status-position bottom
set-option -g status-style bg=''${BG},fg=''${FG}
set-option -g status-justify left set-option -g status-justify left
set-option -g status-left "" set-option -g status-left ""
set-option -g status-right "#(~/.config/tmux/session-list.sh)" set-option -g status-right "#(~/.config/tmux/session-list.sh)"
set-option -g status-left-length 100 set-option -g status-left-length 100
set-option -g status-right-length 100 set-option -g status-right-length 100
source-file "${theme.paths.tmuxCurrentFile}"
set-option -g window-status-format "\
\
#I\
#[fg=''${MAGENTA}]:\
#[fg=default]#W\
\
"
set-option -g window-status-current-format "\
\
#[fg=''${MAGENTA}]*#[fg=default]#I\
#[fg=''${MAGENTA}]:\
#[fg=default]#W\
\
"
set-option -g window-status-separator ""
set-option -g pane-border-style fg=''${BG}
set-option -g pane-active-border-style fg=''${BG}
''; '';
}; };
@ -131,9 +98,10 @@
text = '' text = ''
#!/bin/sh #!/bin/sh
current=$(tmux display-message -p '#S') current=$(tmux display-message -p '#S')
accent=$(tmux show -gv @cozybox-accent 2>/dev/null || printf '#d3869b')
tmux list-sessions -F '#S' | while IFS= read -r s; do tmux list-sessions -F '#S' | while IFS= read -r s; do
if [ "$s" = "$current" ]; then if [ "$s" = "$current" ]; then
printf ' #[fg=#d3869b]*#[fg=default]%s ' "$s" printf ' #[fg=%s]*#[fg=default]%s ' "$accent" "$s"
else else
printf ' %s ' "$s" printf ' %s ' "$s"
fi fi

View file

@ -41,11 +41,11 @@
tailscale = "/Applications/Tailscale.app/Contents/MacOS/Tailscale"; tailscale = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
# nix helpers # nix helpers
nr = "nix profile remove"; # nr <index> - remove from profile nr = "nix profile remove"; # nr <index> - remove from profile
ns = "nix search nixpkgs"; # ns <query> - search packages ns = "nix search nixpkgs"; # ns <query> - search packages
nls = "nix profile list"; # nls - list installed profile packages nls = "nix profile list"; # nls - list installed profile packages
nrb = "sudo darwin-rebuild switch --flake ~/Documents/GitHub/nix"; # nrb - rebuild declarative config nrb = "sudo darwin-rebuild switch --flake ~/Documents/GitHub/nix"; # nrb - rebuild declarative config
nup = "nix flake update ~/Documents/GitHub/nix && sudo darwin-rebuild switch --flake ~/Documents/GitHub/nix"; # nup - update flake + rebuild nup = "nix flake update ~/Documents/GitHub/nix && sudo darwin-rebuild switch --flake ~/Documents/GitHub/nix"; # nup - update flake + rebuild
}; };
envExtra = '' envExtra = ''
@ -108,16 +108,7 @@
$path $path
) )
ni() { nix profile add "nixpkgs#$1"; }
unalias ga 2>/dev/null unalias ga 2>/dev/null
ga() {
if [[ $# -eq 0 ]]; then
git add .
else
git add "$@"
fi
}
git() { git() {
command git "$@" command git "$@"
@ -169,136 +160,9 @@
_codex_set_cursor beam _codex_set_cursor beam
} }
iosrun() {
local project=$(find . -maxdepth 1 -name "*.xcodeproj" | head -1)
local scheme=$(basename "$project" .xcodeproj)
local derived=".derived-data"
local sim_name="''${1:-iPhone 16e}"
if [[ -z "$project" ]]; then
echo "No .xcodeproj found in current directory"
return 1
fi
echo "Building $scheme..."
if ! xcodebuild -project "$project" -scheme "$scheme" \
-destination "platform=iOS Simulator,name=$sim_name" \
-derivedDataPath "$derived" build -quiet; then
echo "Build failed"
return 1
fi
echo "Build succeeded. Launching simulator..."
xcrun simctl boot "$sim_name" 2>/dev/null
open -a Simulator
local app_path="$derived/Build/Products/Debug-iphonesimulator/$scheme.app"
local bundle_id=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$app_path/Info.plist")
echo "Installing $scheme..."
while ! xcrun simctl install "$sim_name" "$app_path" 2>/dev/null; do
sleep 0.5
done
echo "Launching $bundle_id..."
while ! xcrun simctl launch "$sim_name" "$bundle_id" 2>&1 | grep -q "$bundle_id"; do
sleep 0.5
done
echo "Launched $bundle_id - streaming logs (Ctrl+C to stop)"
echo "----------------------------------------"
xcrun simctl spawn "$sim_name" log stream \
--predicate "(subsystem CONTAINS '$bundle_id' OR process == '$scheme') AND NOT subsystem BEGINSWITH 'com.apple'" \
--style compact \
--color always 2>/dev/null | while read -r line; do
if [[ "$line" == *"error"* ]] || [[ "$line" == *"Error"* ]]; then
echo "\033[31m$line\033[0m"
elif [[ "$line" == *"warning"* ]] || [[ "$line" == *"Warning"* ]]; then
echo "\033[33m$line\033[0m"
else
echo "$line"
fi
done
}
mdview() {
markserv "$1"
}
if command -v wt >/dev/null 2>&1; then if command -v wt >/dev/null 2>&1; then
eval "$(command wt config shell init zsh)" eval "$(command wt config shell init zsh)"
fi fi
wtc() { wt switch --create --base @ "$@"; }
unalias gpr 2>/dev/null
gpr() {
while true; do
local pr=$(gh pr list --limit 50 \
--json number,title,author,headRefName \
--template '{{range .}}#{{.number}} {{.title}} ({{.author.login}}) [{{.headRefName}}]{{"\n"}}{{end}}' \
| fzf --preview 'gh pr view {1} --comments' \
--preview-window=right:60%:wrap \
--header 'enter: view | ctrl-m: merge | ctrl-x: close | ctrl-o: checkout | ctrl-b: browser' \
--bind 'ctrl-o:execute(gh pr checkout {1})' \
--bind 'ctrl-b:execute(gh pr view {1} --web)' \
--expect=ctrl-m,ctrl-x,enter)
[[ -z "$pr" ]] && return
local key=$(echo "$pr" | head -1)
local selection=$(echo "$pr" | tail -1)
local num=$(echo "$selection" | grep -o '#[0-9]*' | tr -d '#')
[[ -z "$num" ]] && return
case "$key" in
ctrl-m)
echo "Merge PR #$num? (y/n)"
read -q && gh pr merge "$num" --merge
echo
;;
ctrl-x)
echo "Close PR #$num? (y/n)"
read -q && gh pr close "$num"
echo
;;
enter|"")
gh pr view "$num"
;;
esac
done
}
ghpr() {
local base=$(git rev-parse --abbrev-ref HEAD)
local upstream="''${1:-main}"
local remote_ref="origin/$upstream"
local unpushed=$(git log "$remote_ref"..HEAD --oneline 2>/dev/null)
if [[ -z "$unpushed" ]]; then
if git diff --cached --quiet; then
echo "No unpushed commits and no staged changes"
return 1
fi
echo "No unpushed commits, but staged changes found. Opening commit dialog..."
git commit || return 1
fi
local msg=$(git log "$remote_ref"..HEAD --format='%s' --reverse | head -1)
local branch=$(echo "$msg" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
git checkout -b "$branch"
git checkout "$base"
git reset --hard "$remote_ref"
git checkout "$branch"
git push -u origin "$branch"
gh pr create --base "$upstream" --fill --web 2>/dev/null || gh pr create --base "$upstream" --fill
gh pr view "$branch" --json url -q '.url'
}
'') '')
(lib.mkAfter '' (lib.mkAfter ''

88
lib/theme.nix Normal file
View file

@ -0,0 +1,88 @@
{config, ...}: let
defaultMode = "dark";
paths = {
stateDir = "${config.xdg.stateHome}/theme";
stateFile = "${config.xdg.stateHome}/theme/current";
ghosttyDir = "${config.xdg.configHome}/ghostty/themes";
ghosttyCurrentFile = "${config.xdg.configHome}/ghostty/themes/current.conf";
tmuxDir = "${config.xdg.configHome}/tmux/theme";
tmuxCurrentFile = "${config.xdg.configHome}/tmux/theme/current.conf";
};
themes = {
dark = {
ghosttyTheme = "Gruvbox Material Dark";
background = "#181818";
surface = "#1e1e1e";
selectionBackground = "#504945";
selectionForeground = "#ebdbb2";
cursorColor = "#ddc7a1";
text = "#d4be98";
mutedText = "#7c6f64";
red = "#ea6962";
green = "#8ec97c";
yellow = "#d8a657";
blue = "#5b84de";
aqua = "#8ec07c";
purple = "#d3869b";
orange = "#e78a4e";
border = "#181818";
};
light = {
ghosttyTheme = "Gruvbox Material Light";
background = "#e7e7e7";
surface = "#e1e1e1";
selectionBackground = "#c3c7c9";
selectionForeground = "#1d2021";
cursorColor = "#282828";
text = "#282828";
mutedText = "#665c54";
red = "#ea6962";
green = "#8ec97c";
yellow = "#d8a657";
blue = "#5b84de";
aqua = "#8ec07c";
purple = "#d3869b";
orange = "#e78a4e";
border = "#c3c7c9";
};
};
renderGhostty = mode: let
theme = themes.${mode};
in ''
theme = "${theme.ghosttyTheme}"
background = ${theme.background}
cursor-color = ${theme.cursorColor}
selection-background = ${theme.selectionBackground}
selection-foreground = ${theme.selectionForeground}
palette = 1=${theme.red}
palette = 2=${theme.green}
palette = 3=${theme.yellow}
palette = 4=${theme.blue}
palette = 5=${theme.purple}
palette = 6=${theme.aqua}
palette = 9=${theme.red}
palette = 10=${theme.green}
palette = 11=${theme.yellow}
palette = 12=${theme.blue}
palette = 13=${theme.purple}
palette = 14=${theme.aqua}
'';
renderTmux = mode: let
theme = themes.${mode};
in ''
set-option -g @cozybox-mode '${mode}'
set-option -g @cozybox-accent '${theme.purple}'
set-option -g status-style bg=${theme.background},fg=${theme.text}
set-option -g window-status-format " #I#[fg=${theme.purple}]:#[fg=default]#W "
set-option -g window-status-current-format " #[fg=${theme.purple}]*#[fg=default]#I#[fg=${theme.purple}]:#[fg=default]#W "
set-option -g window-status-separator ""
set-option -g pane-border-style fg=${theme.border}
set-option -g pane-active-border-style fg=${theme.border}
'';
in {
inherit defaultMode paths renderGhostty renderTmux themes;
}

87
scripts/default.nix Normal file
View file

@ -0,0 +1,87 @@
{
config,
lib,
pkgs,
}: let
theme = import ../lib/theme.nix {inherit config;};
tmuxConfigs = {
dark = pkgs.writeText "tmux-theme-dark.conf" (theme.renderTmux "dark");
light = pkgs.writeText "tmux-theme-light.conf" (theme.renderTmux "light");
};
mkScript = {
file,
name,
runtimeInputs ? [],
replacements ? {},
}:
pkgs.writeShellApplication {
inherit name runtimeInputs;
text =
lib.replaceStrings
(builtins.attrNames replacements)
(builtins.attrValues replacements)
(builtins.readFile file);
};
packages = {
ga = mkScript {
name = "ga";
file = ./ga.sh;
runtimeInputs = with pkgs; [git];
};
ghpr = mkScript {
name = "ghpr";
file = ./ghpr.sh;
runtimeInputs = with pkgs; [gh git gnugrep gnused coreutils];
};
gpr = mkScript {
name = "gpr";
file = ./gpr.sh;
runtimeInputs = with pkgs; [gh fzf gnugrep coreutils];
};
iosrun = mkScript {
name = "iosrun";
file = ./iosrun.sh;
runtimeInputs = with pkgs; [findutils gnugrep coreutils];
};
mdview = mkScript {
name = "mdview";
file = ./mdview.sh;
};
ni = mkScript {
name = "ni";
file = ./ni.sh;
runtimeInputs = with pkgs; [nix];
};
theme = mkScript {
name = "theme";
file = ./theme.sh;
runtimeInputs = with pkgs; [coreutils neovim tmux];
replacements = {
"@DEFAULT_MODE@" = theme.defaultMode;
"@STATE_DIR@" = theme.paths.stateDir;
"@STATE_FILE@" = theme.paths.stateFile;
"@TMUX_DIR@" = theme.paths.tmuxDir;
"@TMUX_CURRENT_FILE@" = theme.paths.tmuxCurrentFile;
"@TMUX_DARK_FILE@" = "${tmuxConfigs.dark}";
"@TMUX_LIGHT_FILE@" = "${tmuxConfigs.light}";
"@TMUX_CONFIG@" = "${config.xdg.configHome}/tmux/tmux.conf";
};
};
wtc = mkScript {
name = "wtc";
file = ./wtc.sh;
};
};
in {
inherit packages theme tmuxConfigs;
}

9
scripts/ga.sh Normal file
View file

@ -0,0 +1,9 @@
if [[ $# -eq 0 ]]; then
git add .
else
git add "$@"
fi
if command -v critic >/dev/null 2>&1; then
( critic review 2>/dev/null & )
fi

26
scripts/ghpr.sh Normal file
View file

@ -0,0 +1,26 @@
base=$(git rev-parse --abbrev-ref HEAD)
upstream="${1:-main}"
remote_ref="origin/$upstream"
unpushed=$(git log "$remote_ref"..HEAD --oneline 2>/dev/null)
if [[ -z "$unpushed" ]]; then
if git diff --cached --quiet; then
echo "No unpushed commits and no staged changes"
exit 1
fi
echo "No unpushed commits, but staged changes found. Opening commit dialog..."
git commit
fi
msg=$(git log "$remote_ref"..HEAD --format='%s' --reverse | head -1)
branch=$(echo "$msg" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
git checkout -b "$branch"
git checkout "$base"
git reset --hard "$remote_ref"
git checkout "$branch"
git push -u origin "$branch"
gh pr create --base "$upstream" --fill --web 2>/dev/null || gh pr create --base "$upstream" --fill
gh pr view "$branch" --json url -q '.url'

39
scripts/gpr.sh Normal file
View file

@ -0,0 +1,39 @@
while true; do
pr=$(
gh pr list --limit 50 \
--json number,title,author,headRefName \
--template '{{range .}}#{{.number}} {{.title}} ({{.author.login}}) [{{.headRefName}}]{{"\n"}}{{end}}' \
| fzf --preview 'gh pr view {1} --comments' \
--preview-window=right:60%:wrap \
--header 'enter: view | ctrl-m: merge | ctrl-x: close | ctrl-o: checkout | ctrl-b: browser' \
--bind 'ctrl-o:execute(gh pr checkout {1})' \
--bind 'ctrl-b:execute(gh pr view {1} --web)' \
--expect=ctrl-m,ctrl-x,enter
)
[[ -z "$pr" ]] && exit 0
key=$(echo "$pr" | head -1)
selection=$(echo "$pr" | tail -1)
num=$(echo "$selection" | grep -o '#[0-9]*' | tr -d '#')
[[ -z "$num" ]] && exit 0
case "$key" in
ctrl-m)
read -r -p "Merge PR #$num? [y/N] " response
if [[ "$response" =~ ^[Yy]$ ]]; then
gh pr merge "$num" --merge
fi
;;
ctrl-x)
read -r -p "Close PR #$num? [y/N] " response
if [[ "$response" =~ ^[Yy]$ ]]; then
gh pr close "$num"
fi
;;
enter|"")
gh pr view "$num"
;;
esac
done

51
scripts/iosrun.sh Normal file
View file

@ -0,0 +1,51 @@
project=$(find . -maxdepth 1 -name "*.xcodeproj" | head -1)
scheme=$(basename "$project" .xcodeproj)
derived=".derived-data"
sim_name="${1:-iPhone 16e}"
if [[ -z "$project" ]]; then
echo "No .xcodeproj found in current directory"
exit 1
fi
echo "Building $scheme..."
if ! xcodebuild -project "$project" -scheme "$scheme" \
-destination "platform=iOS Simulator,name=$sim_name" \
-derivedDataPath "$derived" build -quiet; then
echo "Build failed"
exit 1
fi
echo "Build succeeded. Launching simulator..."
xcrun simctl boot "$sim_name" 2>/dev/null || true
open -a Simulator
app_path="$derived/Build/Products/Debug-iphonesimulator/$scheme.app"
bundle_id=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$app_path/Info.plist")
echo "Installing $scheme..."
while ! xcrun simctl install "$sim_name" "$app_path" 2>/dev/null; do
sleep 0.5
done
echo "Launching $bundle_id..."
while ! xcrun simctl launch "$sim_name" "$bundle_id" 2>&1 | grep -q "$bundle_id"; do
sleep 0.5
done
echo "Launched $bundle_id - streaming logs (Ctrl+C to stop)"
echo "----------------------------------------"
xcrun simctl spawn "$sim_name" log stream \
--predicate "(subsystem CONTAINS '$bundle_id' OR process == '$scheme') AND NOT subsystem BEGINSWITH 'com.apple'" \
--style compact \
--color always 2>/dev/null | while read -r line; do
if [[ "$line" == *"error"* ]] || [[ "$line" == *"Error"* ]]; then
printf '\033[31m%s\033[0m\n' "$line"
elif [[ "$line" == *"warning"* ]] || [[ "$line" == *"Warning"* ]]; then
printf '\033[33m%s\033[0m\n' "$line"
else
echo "$line"
fi
done

1
scripts/mdview.sh Normal file
View file

@ -0,0 +1 @@
exec markserv "$@"

6
scripts/ni.sh Normal file
View file

@ -0,0 +1,6 @@
if [[ $# -ne 1 ]]; then
echo "usage: ni <package>"
exit 1
fi
exec nix profile add "nixpkgs#$1"

71
scripts/theme.sh Normal file
View file

@ -0,0 +1,71 @@
usage() {
echo "usage: theme <dark|light|toggle|current>"
}
read_mode() {
if [[ -f "@STATE_FILE@" ]]; then
mode=$(tr -d '[:space:]' < "@STATE_FILE@")
if [[ "$mode" == "dark" || "$mode" == "light" ]]; then
echo "$mode"
return
fi
fi
echo "@DEFAULT_MODE@"
}
link_mode_assets() {
local mode="$1"
local tmux_target
case "$mode" in
dark)
tmux_target="@TMUX_DARK_FILE@"
;;
light)
tmux_target="@TMUX_LIGHT_FILE@"
;;
*)
echo "invalid mode: $mode" >&2
exit 1
;;
esac
mkdir -p "@STATE_DIR@" "@TMUX_DIR@"
printf '%s\n' "$mode" > "@STATE_FILE@"
ln -sfn "$tmux_target" "@TMUX_CURRENT_FILE@"
if command -v tmux >/dev/null 2>&1 && tmux start-server >/dev/null 2>&1; then
tmux source-file "@TMUX_CONFIG@" >/dev/null 2>&1 || true
fi
for socket in /tmp/nvim-*.sock; do
[[ -S "$socket" ]] || continue
nvim --server "$socket" --remote-send "<Cmd>ThemeSync $mode<CR>" >/dev/null 2>&1 || true
done
}
mode="${1:-current}"
case "$mode" in
dark|light)
;;
toggle)
if [[ "$(read_mode)" == "dark" ]]; then
mode="light"
else
mode="dark"
fi
;;
current)
read_mode
exit 0
;;
*)
usage >&2
exit 1
;;
esac
link_mode_assets "$mode"
printf 'applied %s theme\n' "$mode"

1
scripts/wtc.sh Normal file
View file

@ -0,0 +1 @@
exec wt switch --create --base @ "$@"