mirror of
https://github.com/Matir/skel.git
synced 2026-05-26 13:35:42 -07:00
Compare commits
14 Commits
49a314e388
...
claude/deb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7444f5b97b | ||
|
|
c5e1157f47 | ||
|
|
6b50be84a9 | ||
|
|
1804357162 | ||
|
|
202d871a59 | ||
|
|
467d916f33 | ||
|
|
6d2bfdbcea | ||
|
|
1d0a09c442 | ||
|
|
37c765ae29 | ||
|
|
41f8a49381 | ||
|
|
3f9c41d266 | ||
|
|
69e4b83652 | ||
|
|
74c2472dd2 | ||
|
|
ecc39344ca |
@@ -9,8 +9,9 @@ fi
|
||||
UPDATE_SCRIPT="bin/macos/update_brewfile"
|
||||
|
||||
if [[ -x "$UPDATE_SCRIPT" ]]; then
|
||||
# Run in dry-run mode and see if there's output
|
||||
DIFF_OUTPUT=$("$UPDATE_SCRIPT" --dry-run 2>/dev/null)
|
||||
echo "🔍 Checking Brewfile synchronization..."
|
||||
# Run in dry-run mode with --add-only and see if there's output
|
||||
DIFF_OUTPUT=$("$UPDATE_SCRIPT" --dry-run --add-only 2>/dev/null)
|
||||
if [[ "$DIFF_OUTPUT" == *"Changes detected"* ]]; then
|
||||
echo "⚠️ Brewfile is out of sync with your installed packages."
|
||||
echo " Run '$UPDATE_SCRIPT' to synchronize it."
|
||||
|
||||
16
Brewfile
16
Brewfile
@@ -1,5 +1,6 @@
|
||||
tap "dart-lang/dart"
|
||||
tap "sass/sass"
|
||||
|
||||
brew "ack"
|
||||
brew "acme.sh"
|
||||
brew "age"
|
||||
@@ -7,14 +8,16 @@ brew "autoconf"
|
||||
brew "automake"
|
||||
brew "b2-tools"
|
||||
brew "bat"
|
||||
brew "bazelisk"
|
||||
brew "binwalk"
|
||||
brew "cask"
|
||||
brew "ccache"
|
||||
brew "certbot"
|
||||
brew "cmake"
|
||||
brew "colima"
|
||||
brew "devcontainer"
|
||||
brew "difftastic"
|
||||
brew "dfu-util"
|
||||
brew "difftastic"
|
||||
brew "direnv"
|
||||
brew "duck"
|
||||
brew "earthly"
|
||||
@@ -27,6 +30,7 @@ brew "git-lfs"
|
||||
brew "gnupg"
|
||||
brew "go"
|
||||
brew "gradle"
|
||||
brew "hf"
|
||||
brew "htop"
|
||||
brew "httpie"
|
||||
brew "huggingface-cli"
|
||||
@@ -40,11 +44,12 @@ brew "mosh"
|
||||
brew "neovim"
|
||||
brew "ninja"
|
||||
brew "nmap"
|
||||
brew "protobuf"
|
||||
brew "ollama"
|
||||
brew "p7zip"
|
||||
brew "pipenv"
|
||||
brew "pipx"
|
||||
brew "pkgconf"
|
||||
brew "protobuf"
|
||||
brew "pwgen"
|
||||
brew "pwntools"
|
||||
brew "qemu"
|
||||
@@ -53,7 +58,8 @@ brew "ripgrep"
|
||||
brew "ruby"
|
||||
brew "ruby@3.3"
|
||||
brew "rustup"
|
||||
brew "scroll-reverser"
|
||||
brew "sass/sass/migrator"
|
||||
brew "sass/sass/sass"
|
||||
brew "shellcheck"
|
||||
brew "smartmontools"
|
||||
brew "starship"
|
||||
@@ -64,8 +70,7 @@ brew "wget"
|
||||
brew "yt-dlp"
|
||||
brew "zlib"
|
||||
brew "zsh-syntax-highlighting"
|
||||
brew "sass/sass/migrator"
|
||||
brew "sass/sass/sass"
|
||||
|
||||
cask "codeql"
|
||||
cask "cyberduck"
|
||||
cask "font-fira-code-nerd-font"
|
||||
@@ -82,6 +87,7 @@ cask "iterm2"
|
||||
cask "macfuse"
|
||||
cask "meld"
|
||||
cask "mitmproxy"
|
||||
cask "processmonitor"
|
||||
cask "raycast"
|
||||
cask "rectangle"
|
||||
cask "scroll-reverser"
|
||||
|
||||
@@ -7,16 +7,69 @@ import sys
|
||||
import argparse
|
||||
import difflib
|
||||
|
||||
import tempfile
|
||||
|
||||
# Regex to match brew/cask/tap/mas lines
|
||||
PKG_RE = re.compile(r'^\s*(brew|cask|tap|mas)\s+["\']([^"\']+)["\'](.*)$')
|
||||
|
||||
def colorize_diff(lines):
|
||||
for line in lines:
|
||||
if line.startswith('+') and not line.startswith('+++'):
|
||||
yield f"\033[32m{line}\033[0m"
|
||||
elif line.startswith('-') and not line.startswith('---'):
|
||||
yield f"\033[31m{line}\033[0m"
|
||||
elif line.startswith('^'):
|
||||
yield f"\033[36m{line}\033[0m"
|
||||
else:
|
||||
yield line
|
||||
|
||||
class Entry:
|
||||
def sort_key(self): raise NotImplementedError()
|
||||
def to_lines(self): raise NotImplementedError()
|
||||
|
||||
class PackageEntry(Entry):
|
||||
def __init__(self, pkg_type, name, options, comments=None):
|
||||
self.pkg_type = pkg_type
|
||||
self.name = name
|
||||
self.options = options.strip()
|
||||
self.comments = comments or []
|
||||
|
||||
def sort_key(self):
|
||||
order = {'tap': 0, 'brew': 1, 'cask': 2, 'mas': 3}
|
||||
return (order.get(self.pkg_type, 4), self.name)
|
||||
|
||||
def to_lines(self):
|
||||
res = list(self.comments)
|
||||
pkg_line = f'{self.pkg_type} "{self.name}"'
|
||||
if self.options:
|
||||
if not self.options.startswith(','):
|
||||
pkg_line += ' '
|
||||
pkg_line += self.options
|
||||
res.append(pkg_line)
|
||||
return res
|
||||
|
||||
class TextEntry(Entry):
|
||||
def __init__(self, lines, is_header=True):
|
||||
self.lines = lines
|
||||
self.is_header = is_header
|
||||
|
||||
def sort_key(self):
|
||||
# Header is -1, Trailing is 5 (after all package types 0-4)
|
||||
return (-1 if self.is_header else 5, "")
|
||||
|
||||
def to_lines(self):
|
||||
return self.lines
|
||||
|
||||
def get_repo_root():
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
try:
|
||||
root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'],
|
||||
cwd=script_dir,
|
||||
stderr=subprocess.STDOUT).decode().strip()
|
||||
return root
|
||||
except subprocess.CalledProcessError:
|
||||
return os.getcwd()
|
||||
# If not in a git repo, go up 2 levels from script_dir (bin/macos/)
|
||||
return os.path.abspath(os.path.join(script_dir, '..', '..'))
|
||||
|
||||
def get_ignore_list(repo_root):
|
||||
ignore = set()
|
||||
@@ -34,11 +87,19 @@ def get_ignore_list(repo_root):
|
||||
ignore.add(line)
|
||||
return ignore
|
||||
|
||||
def get_current_packages():
|
||||
def get_current_packages(args):
|
||||
"""Runs brew bundle dump and returns lines."""
|
||||
env = os.environ.copy()
|
||||
env["HOMEBREW_NO_AUTO_UPDATE"] = "1"
|
||||
env["HOMEBREW_NO_INSTALL_CLEANUP"] = "1"
|
||||
env["HOMEBREW_NO_ENV_HINTS"] = "1"
|
||||
|
||||
cmd = ['brew', 'bundle', 'dump', '--file=-']
|
||||
if args.no_mas: cmd.append('--no-mas')
|
||||
if args.no_vscode: cmd.append('--no-vscode')
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(['brew', 'bundle', 'dump', '--file=-'],
|
||||
stderr=subprocess.DEVNULL).decode()
|
||||
output = subprocess.check_output(cmd, env=env, stderr=subprocess.DEVNULL).decode()
|
||||
return output.splitlines()
|
||||
except subprocess.CalledProcessError:
|
||||
print("Error: 'brew bundle dump' failed. Is homebrew installed?", file=sys.stderr)
|
||||
@@ -48,8 +109,9 @@ def parse_brewfile(content):
|
||||
"""
|
||||
Parses Brewfile content.
|
||||
Returns:
|
||||
- entries: list of Entry objects
|
||||
- conditional_pkgs: set of (type, name)
|
||||
- preserved_footer: string (everything from first conditional onwards)
|
||||
- footer: string (everything from first conditional onwards)
|
||||
"""
|
||||
lines = content.splitlines()
|
||||
conditional_pkgs = set()
|
||||
@@ -60,7 +122,7 @@ def parse_brewfile(content):
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith(('if ', 'unless ', 'case ')) and not stripped.endswith('; end'):
|
||||
if stripped.startswith(('if ', 'unless ', 'case ', 'def ', 'begin ')) and not stripped.endswith('; end'):
|
||||
if first_conditional_idx == -1:
|
||||
# Look back for comments that might belong to this block
|
||||
j = i - 1
|
||||
@@ -77,13 +139,35 @@ def parse_brewfile(content):
|
||||
if stripped == 'end' or stripped.endswith('; end'):
|
||||
in_conditional -= 1
|
||||
|
||||
if first_conditional_idx == -1:
|
||||
return set(), ""
|
||||
unconditional_lines = lines[:first_conditional_idx] if first_conditional_idx != -1 else lines
|
||||
footer = "\n".join(lines[first_conditional_idx:]) if first_conditional_idx != -1 else ""
|
||||
|
||||
footer = "\n".join(lines[first_conditional_idx:])
|
||||
return conditional_pkgs, footer
|
||||
entries = []
|
||||
comment_buffer = []
|
||||
first_pkg_seen = False
|
||||
|
||||
for line in unconditional_lines:
|
||||
match = PKG_RE.match(line)
|
||||
if match:
|
||||
if not first_pkg_seen:
|
||||
if comment_buffer:
|
||||
entries.append(TextEntry(comment_buffer, is_header=True))
|
||||
comment_buffer = []
|
||||
first_pkg_seen = True
|
||||
entries.append(PackageEntry(match.group(1), match.group(2), match.group(3), comment_buffer))
|
||||
comment_buffer = []
|
||||
else:
|
||||
comment_buffer.append(line)
|
||||
|
||||
if comment_buffer:
|
||||
entries.append(TextEntry(comment_buffer, is_header=False))
|
||||
|
||||
return entries, conditional_pkgs, footer
|
||||
|
||||
def main(args):
|
||||
if sys.platform != "darwin":
|
||||
print(f"Warning: Running on {sys.platform}. Brewfile is primarily for macOS.", file=sys.stderr)
|
||||
|
||||
repo_root = get_repo_root()
|
||||
brewfile_path = os.path.join(repo_root, 'Brewfile')
|
||||
|
||||
@@ -94,39 +178,84 @@ def main(args):
|
||||
with open(brewfile_path) as f:
|
||||
old_content = f.read()
|
||||
|
||||
conditional_pkgs, footer = parse_brewfile(old_content)
|
||||
old_entries, conditional_pkgs, footer = parse_brewfile(old_content)
|
||||
ignore_list = get_ignore_list(repo_root)
|
||||
|
||||
dumped_lines = get_current_packages()
|
||||
|
||||
new_unconditional_lines = []
|
||||
old_pkg_map = {}
|
||||
for e in old_entries:
|
||||
if isinstance(e, PackageEntry):
|
||||
key = (e.pkg_type, e.name)
|
||||
if key not in old_pkg_map:
|
||||
old_pkg_map[key] = e
|
||||
|
||||
dumped_lines = get_current_packages(args)
|
||||
dumped_pkgs = []
|
||||
for line in dumped_lines:
|
||||
match = PKG_RE.match(line)
|
||||
if match:
|
||||
pkg_type, pkg_name = match.group(1), match.group(2)
|
||||
if pkg_name in ignore_list:
|
||||
continue
|
||||
if (pkg_type, pkg_name) in conditional_pkgs:
|
||||
pkg_type, pkg_name, pkg_options = match.group(1), match.group(2), match.group(3)
|
||||
if pkg_name in ignore_list or (pkg_type, pkg_name) in conditional_pkgs:
|
||||
continue
|
||||
dumped_pkgs.append(PackageEntry(pkg_type, pkg_name, pkg_options))
|
||||
|
||||
# If it's not a package line (e.g. comment from dump), we can skip or keep
|
||||
if line.strip():
|
||||
new_unconditional_lines.append(line)
|
||||
new_entries = []
|
||||
for e in old_entries:
|
||||
if isinstance(e, TextEntry) and e.is_header:
|
||||
new_entries.append(e)
|
||||
|
||||
# Sort lines by type (tap, brew, cask, mas) then name
|
||||
def sort_key(line):
|
||||
match = PKG_RE.match(line)
|
||||
if not match: return (4, line)
|
||||
order = {'tap': 0, 'brew': 1, 'cask': 2, 'mas': 3}
|
||||
return (order.get(match.group(1), 4), match.group(2))
|
||||
seen_in_new = set()
|
||||
added_count = 0
|
||||
removed_count = 0
|
||||
merged_count = 0
|
||||
|
||||
new_unconditional_lines.sort(key=sort_key)
|
||||
if args.add_only:
|
||||
for e in old_entries:
|
||||
if isinstance(e, PackageEntry):
|
||||
new_entries.append(e)
|
||||
seen_in_new.add((e.pkg_type, e.name))
|
||||
for d in dumped_pkgs:
|
||||
if (d.pkg_type, d.name) not in seen_in_new:
|
||||
new_entries.append(d)
|
||||
seen_in_new.add((d.pkg_type, d.name))
|
||||
added_count += 1
|
||||
else:
|
||||
for d in dumped_pkgs:
|
||||
key = (d.pkg_type, d.name)
|
||||
if key in seen_in_new: continue
|
||||
if key in old_pkg_map:
|
||||
merged = old_pkg_map[key]
|
||||
if not merged.options:
|
||||
merged.options = d.options
|
||||
new_entries.append(merged)
|
||||
merged_count += 1
|
||||
else:
|
||||
new_entries.append(d)
|
||||
added_count += 1
|
||||
seen_in_new.add(key)
|
||||
|
||||
# Build new content
|
||||
new_content = "\n".join(new_unconditional_lines)
|
||||
# Check for removals
|
||||
for key in old_pkg_map:
|
||||
if key not in seen_in_new:
|
||||
removed_count += 1
|
||||
|
||||
for e in old_entries:
|
||||
if isinstance(e, TextEntry) and not e.is_header:
|
||||
new_entries.append(e)
|
||||
|
||||
new_entries.sort(key=lambda x: x.sort_key())
|
||||
|
||||
output_lines = []
|
||||
last_type = None
|
||||
for e in new_entries:
|
||||
if isinstance(e, PackageEntry):
|
||||
if last_type and e.pkg_type != last_type:
|
||||
output_lines.append("")
|
||||
last_type = e.pkg_type
|
||||
output_lines.extend(e.to_lines())
|
||||
|
||||
new_content = "\n".join(output_lines)
|
||||
if footer:
|
||||
if new_unconditional_lines:
|
||||
if output_lines and output_lines[-1].strip():
|
||||
new_content += "\n\n"
|
||||
new_content += footer.strip() + "\n"
|
||||
else:
|
||||
@@ -137,20 +266,33 @@ def main(args):
|
||||
else:
|
||||
if args.dry_run:
|
||||
print("Changes detected (dry run):")
|
||||
diff = difflib.unified_diff(
|
||||
diff = list(difflib.unified_diff(
|
||||
old_content.splitlines(keepends=True),
|
||||
new_content.splitlines(keepends=True),
|
||||
fromfile='Brewfile (original)',
|
||||
tofile='Brewfile (new)'
|
||||
)
|
||||
sys.stdout.writelines(diff)
|
||||
))
|
||||
if sys.stdout.isatty():
|
||||
sys.stdout.writelines(colorize_diff(diff))
|
||||
else:
|
||||
sys.stdout.writelines(diff)
|
||||
else:
|
||||
with open(brewfile_path, 'w') as f:
|
||||
f.write(new_content)
|
||||
dir_name = os.path.dirname(brewfile_path)
|
||||
with tempfile.NamedTemporaryFile('w', dir=dir_name, delete=False) as tf:
|
||||
tf.write(new_content)
|
||||
tempname = tf.name
|
||||
os.replace(tempname, brewfile_path)
|
||||
print("Brewfile updated.")
|
||||
|
||||
if args.verbose:
|
||||
print(f"Summary: {added_count} added, {removed_count} removed, {merged_count} kept/merged.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Update Brewfile while preserving conditionals.")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Show changes without applying them.")
|
||||
parser.add_argument("--add-only", action="store_true", help="Only add missing entries, do not remove existing ones.")
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Print summary of changes.")
|
||||
parser.add_argument("--no-mas", action="store_true", help="Do not include Mac App Store apps.")
|
||||
parser.add_argument("--no-vscode", action="store_true", help="Do not include VSCode extensions.")
|
||||
args = parser.parse_args()
|
||||
main(args)
|
||||
|
||||
95
dotfiles/config/fish/conf.d/gemini.fish
Normal file
95
dotfiles/config/fish/conf.d/gemini.fish
Normal file
@@ -0,0 +1,95 @@
|
||||
# Bridge to Gemini CLI Context Management (Zsh-backed)
|
||||
# This allows using the Zsh implementation from Fish to avoid dual maintenance.
|
||||
|
||||
# Ensure the base context directory exists
|
||||
if set -q XDG_CONFIG_HOME
|
||||
set -gx GEMINI_CONTEXT_DIR $XDG_CONFIG_HOME/gemini
|
||||
else
|
||||
set -gx GEMINI_CONTEXT_DIR $HOME/.config/gemini
|
||||
end
|
||||
mkdir -p "$GEMINI_CONTEXT_DIR"
|
||||
|
||||
# Path to the source of truth
|
||||
set -l gemini_zsh_script "$HOME/.skel/dotfiles/zshrc.d/gemini.zsh"
|
||||
|
||||
function _gemini_context_bridge
|
||||
# Check if Zsh script exists
|
||||
if not test -f "$gemini_zsh_script"
|
||||
echo "Error: Gemini Zsh script not found at $gemini_zsh_script"
|
||||
return 1
|
||||
end
|
||||
|
||||
# We use a temporary file to reliably pass the environment variable back
|
||||
set -l env_file (mktemp -t gemini_context.XXXXXX)
|
||||
|
||||
# Run zsh with -e (exit on error):
|
||||
# 1. Source the context manager
|
||||
# 2. Run the requested command with arguments
|
||||
# 3. Write the resulting GEMINI_CLI_HOME to the temp file
|
||||
command zsh -ec "
|
||||
source '$gemini_zsh_script'
|
||||
$argv
|
||||
echo \"\$GEMINI_CLI_HOME\" > '$env_file'
|
||||
"
|
||||
set -l zsh_status $status
|
||||
|
||||
# Only sync environment and clean up on success
|
||||
if test $zsh_status -eq 0
|
||||
set -l new_home (cat $env_file | string trim)
|
||||
if test -n "$new_home"
|
||||
set -gx GEMINI_CLI_HOME "$new_home"
|
||||
else
|
||||
set -e GEMINI_CLI_HOME
|
||||
end
|
||||
end
|
||||
|
||||
rm -f $env_file
|
||||
return $zsh_status
|
||||
end
|
||||
|
||||
# Wrapper functions
|
||||
function gemini-context-use
|
||||
_gemini_context_bridge "gemini-context-use $argv"
|
||||
end
|
||||
|
||||
function gemini-context-create
|
||||
_gemini_context_bridge "gemini-context-create $argv"
|
||||
end
|
||||
|
||||
function gemini-context-list
|
||||
_gemini_context_bridge "gemini-context-list $argv"
|
||||
end
|
||||
|
||||
function gemini-context-delete
|
||||
_gemini_context_bridge "gemini-context-delete $argv"
|
||||
end
|
||||
|
||||
function gemini-context-rename
|
||||
_gemini_context_bridge "gemini-context-rename $argv"
|
||||
end
|
||||
|
||||
function gemini-context-edit
|
||||
_gemini_context_bridge "gemini-context-edit $argv"
|
||||
end
|
||||
|
||||
function gemini-context-current
|
||||
_gemini_context_bridge "gemini-context-current $argv"
|
||||
end
|
||||
|
||||
function gemini-context-unset
|
||||
_gemini_context_bridge "gemini-context-unset $argv"
|
||||
end
|
||||
|
||||
# Aliases
|
||||
alias gemctx='gemini-context-use'
|
||||
|
||||
# Completion
|
||||
function _gemini_context_list
|
||||
zsh -c "source '$gemini_zsh_script'; _gemini_context_list_internal"
|
||||
end
|
||||
|
||||
complete -c gemini-context-use -f -a "(_gemini_context_list)"
|
||||
complete -c gemctx -f -a "(_gemini_context_list)"
|
||||
complete -c gemini-context-edit -f -a "(_gemini_context_list)"
|
||||
complete -c gemini-context-delete -f -a "(_gemini_context_list)"
|
||||
complete -c gemini-context-rename -f -a "(_gemini_context_list)"
|
||||
@@ -30,5 +30,8 @@ if status --is-interactive
|
||||
install_fisher
|
||||
end
|
||||
|
||||
# Want this at the bottom to put this path first
|
||||
# Want these at the bottom to put them first in PATH
|
||||
fish_add_path --move --path {$HOME}/bin
|
||||
if test (uname) = "Darwin"
|
||||
fish_add_path --move --path {$HOME}/bin/macos
|
||||
end
|
||||
|
||||
@@ -51,3 +51,4 @@ fi
|
||||
"""
|
||||
style = "bold blue"
|
||||
format = "♊[$output](blue) "
|
||||
shell = ["/bin/sh", "-c"]
|
||||
|
||||
9
dotfiles/gitconfig.d/aliases
Normal file
9
dotfiles/gitconfig.d/aliases
Normal file
@@ -0,0 +1,9 @@
|
||||
[alias]
|
||||
commit-assumed = "!f() { \
|
||||
file=\"$1\"; \
|
||||
shift; \
|
||||
git update-index --no-assume-unchanged \"$file\" && \
|
||||
git add \"$file\" && \
|
||||
git commit \"$@\" && \
|
||||
git update-index --assume-unchanged \"$file\"; \
|
||||
}; f"
|
||||
@@ -113,13 +113,17 @@ _is_link_path() {
|
||||
|
||||
_CANDIDATE=""
|
||||
|
||||
# 1. If current environment has a valid socket that is NOT our link, it's a prime candidate (e.g. SSH forwarding).
|
||||
# 1. If current environment has a valid socket that is NOT our link, it's a prime candidate
|
||||
# (e.g. fresh SSH login: sshd sets SSH_AUTH_SOCK to the raw forwarded socket before ssh/rc
|
||||
# rewrites it to the stable symlink; the shell inherits the original raw path).
|
||||
if [ -S "${SSH_AUTH_SOCK:-}" ] && ! _is_link_path "${SSH_AUTH_SOCK}"; then
|
||||
_CANDIDATE="${SSH_AUTH_SOCK}"
|
||||
fi
|
||||
|
||||
# 2. If no candidate yet, or we're currently using the link, try to find the "real" system agent.
|
||||
if [ -z "${_CANDIDATE}" ] || _is_link_path "${SSH_AUTH_SOCK:-}"; then
|
||||
# 2. Only look for a system agent if the stable link is already broken. If the link is
|
||||
# valid (e.g. a tmux pane where SSH_AUTH_SOCK points to our symlink which ssh/rc just
|
||||
# updated to the forwarded socket), leave it alone — don't clobber it with a local agent.
|
||||
if [ -z "${_CANDIDATE}" ] && [ ! -S "${_SSH_AUTH_LINK}" ]; then
|
||||
_FOUND=""
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
_FOUND=$(launchctl getenv SSH_AUTH_SOCK 2>/dev/null)
|
||||
@@ -143,8 +147,8 @@ if [ -z "${_CANDIDATE}" ] || _is_link_path "${SSH_AUTH_SOCK:-}"; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Last resort: search common paths if we still don't have a valid candidate.
|
||||
if [ ! -S "${_CANDIDATE}" ]; then
|
||||
# 3. Last resort: search common paths if we still don't have a candidate and the link is broken.
|
||||
if [ ! -S "${_CANDIDATE}" ] && [ ! -S "${_SSH_AUTH_LINK}" ]; then
|
||||
_U=$(id -u)
|
||||
for _p in "/run/user/${_U}/keyring/ssh" "/run/user/${_U}/ssh-agent.socket" "/run/user/${_U}/openssh_agent" "/run/user/${_U}/gnupg/S.gpg-agent.ssh"; do
|
||||
if [ -S "${_p}" ] && ! _is_link_path "${_p}"; then
|
||||
@@ -189,6 +193,7 @@ if [ "$(uname)" = "Darwin" ] ; then
|
||||
# Using id -u for better POSIX compatibility than $UID
|
||||
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-$TMPDIR/runtime-$(id -u)}"
|
||||
export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
|
||||
export PATH="${HOME}/bin/macos:${PATH}"
|
||||
fi
|
||||
|
||||
if test -e "$HOME/.localenv"; then
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
# Roughly based on this article:
|
||||
# https://werat.github.io/2017/02/04/tmux-ssh-agent-forwarding.html
|
||||
#
|
||||
# NOTE: this file is executed by sshd as a child process, NOT sourced by the
|
||||
# user's shell. Any variable assignments or exports here have no effect on the
|
||||
# shell environment the user will land in.
|
||||
|
||||
REMOTE_LINK="${HOME}/.ssh/ssh_auth_sock"
|
||||
|
||||
if [ -S "${SSH_AUTH_SOCK}" ] ; then
|
||||
SSH_REMOTE_AUTH_SOCK="${SSH_AUTH_SOCK}"
|
||||
export SSH_REMOTE_AUTH_SOCK
|
||||
# Always update the symlink to the latest session's socket.
|
||||
# This ensures that tmux (which uses the static path) always points to a
|
||||
# current agent.
|
||||
mkdir -p "$(dirname "${REMOTE_LINK}")"
|
||||
ln -sf "${SSH_AUTH_SOCK}" "${REMOTE_LINK}"
|
||||
SSH_AUTH_SOCK="${REMOTE_LINK}"
|
||||
export SSH_AUTH_SOCK
|
||||
fi
|
||||
|
||||
# if stdin is a tty, don't do the cookie step
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# For interactive shells
|
||||
[[ -n "$ZSH_PROFILE" ]] && {
|
||||
zshrc_start_time=$(date +%s.%N)
|
||||
zmodload zsh/datetime
|
||||
zshrc_start_time=$EPOCHREALTIME
|
||||
zmodload zsh/zprof
|
||||
}
|
||||
HISTFILE=~/.zhistory
|
||||
@@ -191,6 +192,15 @@ if [[ $- == *i* ]] ; then
|
||||
source_if_existing $HOME/.aliases.local
|
||||
# zsh-only-ism to avoid error if glob doesn't expand
|
||||
# specifically sets NULLGLOB for this one glob
|
||||
typeset -gA _deferred_comps
|
||||
compdef() {
|
||||
# Store the arguments: first arg is the function, the rest are the commands
|
||||
local func=$1
|
||||
shift
|
||||
for cmd in "$@"; do
|
||||
_deferred_comps[$cmd]=$func
|
||||
done
|
||||
}
|
||||
for file in $HOME/.zshrc.d/[a-zA-Z0-9]*.zsh(N) ; do
|
||||
source "$file"
|
||||
done
|
||||
@@ -205,19 +215,27 @@ if [[ $- == *i* ]] ; then
|
||||
zstyle ':completion:*' users root ${USER}
|
||||
# Modules after fpath
|
||||
autoload -Uz compinit
|
||||
for cmd func in "${(@kv)_deferred_comps}"; do
|
||||
compdef "$func" "$cmd"
|
||||
done
|
||||
|
||||
unset _deferred_comps
|
||||
|
||||
# Regenerate zcompdump if it's older than any file in fpath
|
||||
DUMPFILE="${ZDOTDIR:-$HOME}/.zcompdump"
|
||||
updated_files=(${^fpath}(N.om[1]))
|
||||
# Find the newest file across all fpath directories to detect edits/additions
|
||||
# (N.om[1]) = Null-glob, plain files only, sort by mtime, pick the first (newest)
|
||||
local newest_comp=(${^fpath}/*(N.om[1]))
|
||||
|
||||
if [[ ! -f "$DUMPFILE" || ( ${#updated_files} -gt 0 && "$updated_files[1]" -nt "$DUMPFILE" ) ]]; then
|
||||
compinit -i -D "$DUMPFILE"
|
||||
# Asynchronously compile the dump file
|
||||
{ zcompile "$DUMPFILE" } &!
|
||||
# If any completion file was modified, or dump doesn't exist, we might need to update.
|
||||
if [[ ! -f "$DUMPFILE" || ( -n "$newest_comp" && "$newest_comp" -nt "$DUMPFILE" ) ]]; then
|
||||
compinit -i -d "$DUMPFILE"
|
||||
# Asynchronously compile the dump file with an atomic rename
|
||||
{ zcompile "$DUMPFILE.zwc.tmp" "$DUMPFILE" && mv -f "$DUMPFILE.zwc.tmp" "$DUMPFILE.zwc" } &!
|
||||
else
|
||||
compinit -C -i -D "$DUMPFILE"
|
||||
compinit -C -i -d "$DUMPFILE"
|
||||
fi
|
||||
unset DUMPFILE updated_files
|
||||
unset DUMPFILE newest_comp
|
||||
autoload -Uz promptinit && promptinit
|
||||
# Virtualenvwrapper
|
||||
source_first_existing \
|
||||
@@ -267,8 +285,11 @@ if [ -x /usr/bin/ack-grep ] ; then
|
||||
alias ack='/usr/bin/ack-grep'
|
||||
fi
|
||||
|
||||
# I want this first always
|
||||
# I want these first always
|
||||
PATH="${HOME}/bin:${PATH}"
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
PATH="${HOME}/bin/macos:${PATH}"
|
||||
fi
|
||||
|
||||
# Load any local settings
|
||||
source_if_existing $HOME/.zshrc.local
|
||||
@@ -292,9 +313,9 @@ fi
|
||||
typeset -U PATH
|
||||
|
||||
if [[ -n "$ZSH_PROFILE" ]]; then
|
||||
zshrc_end_time=$(date +%s.%N)
|
||||
elapsed_seconds=$(echo "$zshrc_end_time - $zshrc_start_time" | bc -l)
|
||||
elapsed_ms=$(printf "%.0f" "$(echo "$elapsed_seconds * 1000" | bc -l)")
|
||||
echo "zshrc done: ${elapsed_ms}ms"
|
||||
zshrc_end_time=$EPOCHREALTIME
|
||||
# Calculation in ms using zsh floating point math
|
||||
elapsed_ms=$(( (zshrc_end_time - zshrc_start_time) * 1000 ))
|
||||
printf "zshrc done: %.0fms\n" "$elapsed_ms"
|
||||
zprof
|
||||
fi
|
||||
|
||||
@@ -7,7 +7,7 @@ export GEMINI_CONTEXT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/gemini"
|
||||
mkdir -p "$GEMINI_CONTEXT_DIR"
|
||||
|
||||
# Template settings file
|
||||
GEMINI_TEMPLATE_SETTINGS="${HOME}/.skel/dotfiles/config/gemini/settings.json"
|
||||
GEMINI_TEMPLATE_SETTINGS="${GEMINI_CONTEXT_DIR}/settings.json"
|
||||
|
||||
# Create a new Gemini context
|
||||
gemini-context-create() {
|
||||
@@ -19,8 +19,13 @@ gemini-context-create() {
|
||||
local context_name="$1"
|
||||
local context_path="$GEMINI_CONTEXT_DIR/$context_name"
|
||||
|
||||
if [[ "$context_name" =~ [/] ]]; then
|
||||
echo "Error: Context name cannot contain slashes."
|
||||
if [[ "$context_name" =~ [^a-zA-Z0-9_-] ]]; then
|
||||
echo "Error: Context name should only contain alphanumeric characters, underscores, or dashes."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$context_name" == .* ]]; then
|
||||
echo "Error: Context name cannot start with a dot."
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -42,25 +47,90 @@ gemini-context-create() {
|
||||
echo "Context '$context_name' created."
|
||||
}
|
||||
|
||||
# List available Gemini contexts
|
||||
# List available Gemini contexts (directories only, containing .gemini)
|
||||
_gemini_context_list_internal() {
|
||||
command ls "$GEMINI_CONTEXT_DIR" 2>/dev/null
|
||||
local contexts
|
||||
# Use Zsh globbing: find directories containing a .gemini subdir
|
||||
contexts=($GEMINI_CONTEXT_DIR/*/.gemini(N/:h:t))
|
||||
[[ ${#contexts} -gt 0 ]] && print -l "${contexts[@]}"
|
||||
}
|
||||
|
||||
gemini-context-list() {
|
||||
echo "Available Gemini contexts:"
|
||||
local contexts=$(_gemini_context_list_internal)
|
||||
if [ -z "$contexts" ]; then
|
||||
local contexts=($(_gemini_context_list_internal))
|
||||
if [ ${#contexts} -eq 0 ]; then
|
||||
echo " (No contexts found)"
|
||||
return
|
||||
fi
|
||||
for context in $contexts; do
|
||||
if [ -d "$GEMINI_CONTEXT_DIR/$context" ]; then
|
||||
echo " $context"
|
||||
for context in "${contexts[@]}"; do
|
||||
if [ "$GEMINI_CLI_HOME" = "$GEMINI_CONTEXT_DIR/$context" ]; then
|
||||
echo " * $context (active)"
|
||||
else
|
||||
echo " $context"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Delete a Gemini context
|
||||
gemini-context-delete() {
|
||||
local context_name
|
||||
if [ -z "$1" ]; then
|
||||
if command -v fzf >/dev/null; then
|
||||
context_name=$(_gemini_context_list_internal | fzf --prompt="Select Gemini Context to DELETE: ")
|
||||
[[ -z "$context_name" ]] && return 1
|
||||
else
|
||||
echo "Usage: gemini-context-delete <name>"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
context_name="$1"
|
||||
fi
|
||||
|
||||
local context_path="$GEMINI_CONTEXT_DIR/$context_name"
|
||||
if [ ! -d "$context_path" ]; then
|
||||
echo "Error: Context '$context_name' not found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if read -q "confirm?Are you sure you want to delete context '$context_name'? [y/N] "; then
|
||||
echo
|
||||
rm -rf "$context_path"
|
||||
echo "Context '$context_name' deleted."
|
||||
if [ "$GEMINI_CLI_HOME" = "$context_path" ]; then
|
||||
gemini-context-unset
|
||||
fi
|
||||
else
|
||||
echo "\nDeletion cancelled."
|
||||
fi
|
||||
}
|
||||
|
||||
# Rename a Gemini context
|
||||
gemini-context-rename() {
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
echo "Usage: gemini-context-rename <old_name> <new_name>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local old_path="$GEMINI_CONTEXT_DIR/$1"
|
||||
local new_path="$GEMINI_CONTEXT_DIR/$2"
|
||||
|
||||
if [ ! -d "$old_path" ]; then
|
||||
echo "Error: Source context '$1' not found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -d "$new_path" ]; then
|
||||
echo "Error: Destination context '$2' already exists."
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv "$old_path" "$new_path"
|
||||
echo "Context '$1' renamed to '$2'."
|
||||
if [ "$GEMINI_CLI_HOME" = "$old_path" ]; then
|
||||
gemini-context-use -q "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
# Use a specific Gemini context
|
||||
gemini-context-use() {
|
||||
local context_name
|
||||
@@ -89,14 +159,13 @@ gemini-context-use() {
|
||||
|
||||
local context_path="$GEMINI_CONTEXT_DIR/$context_name"
|
||||
|
||||
if [ ! -d "$context_path" ]; then
|
||||
[[ "$quiet" -eq 0 ]] && echo "Error: Context '$context_name' not found at $context_path"
|
||||
[[ "$quiet" -eq 0 ]] && gemini-context-list
|
||||
if [ ! -d "$context_path/.gemini" ]; then
|
||||
[[ "$quiet" -eq 0 ]] && echo "Error: '$context_name' is not a valid Gemini context (missing .gemini directory)."
|
||||
return 1
|
||||
fi
|
||||
|
||||
export GEMINI_CLI_HOME="$context_path"
|
||||
[[ "$quiet" -eq 0 ]] && echo "Switched to Gemini context '$context_name' (GEMINI_CLI_HOME=$GEMINI_CLI_HOME)"
|
||||
[[ "$quiet" -eq 0 ]] && echo "Switched to Gemini context '$context_name'"
|
||||
}
|
||||
|
||||
# Edit a context's settings
|
||||
@@ -136,13 +205,12 @@ gemini-context-edit() {
|
||||
# Show the current Gemini context
|
||||
gemini-context-current() {
|
||||
if [ -n "$GEMINI_CLI_HOME" ]; then
|
||||
local current_context=$(basename "$GEMINI_CLI_HOME")
|
||||
local current_context=${GEMINI_CLI_HOME:t}
|
||||
if [ "$GEMINI_CONTEXT_DIR/$current_context" = "$GEMINI_CLI_HOME" ]; then
|
||||
echo "Current Gemini context: $current_context"
|
||||
else
|
||||
echo "Current Gemini context: Custom"
|
||||
echo "Current Gemini context: Custom ($GEMINI_CLI_HOME)"
|
||||
fi
|
||||
echo "GEMINI_CLI_HOME=$GEMINI_CLI_HOME"
|
||||
else
|
||||
echo "No Gemini context set, using default."
|
||||
fi
|
||||
@@ -157,12 +225,12 @@ gemini-context-unset() {
|
||||
# Alias for gemini-context-use
|
||||
alias gemctx='gemini-context-use'
|
||||
|
||||
# Zsh Completion for gemini-context-use and gemctx
|
||||
# Zsh Completion
|
||||
_gemini_contexts() {
|
||||
local contexts
|
||||
local -a contexts
|
||||
contexts=($(_gemini_context_list_internal))
|
||||
_describe 'gemini contexts' contexts
|
||||
}
|
||||
compdef _gemini_contexts gemini-context-use
|
||||
compdef _gemini_contexts gemctx
|
||||
compdef _gemini_contexts gemini-context-edit
|
||||
|
||||
compdef _gemini_contexts gemini-context-use gemctx gemini-context-edit gemini-context-delete gemini-context-rename
|
||||
|
||||
|
||||
Reference in New Issue
Block a user