Commit a971262acd37
Changed files (2)
dots
config
raffi
scripts
dots/config/raffi/scripts/lazypr-raffi
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+"""List git repositories with GitHub remotes under ~/src/ for raffi script filter.
+
+Outputs Alfred Script Filter JSON format.
+Results are cached for 2 hours.
+Usage: lazypr-raffi [query]
+"""
+
+import json
+import os
+import subprocess
+import sys
+import tempfile
+import time
+
+CACHE_MAX_AGE = 7200 # 2 hours
+SRC_DIR = os.path.expanduser("~/src")
+
+
+def cache_paths():
+ cache_home = os.environ.get("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
+ cache_dir = os.path.join(cache_home, "lazypr-raffi")
+ os.makedirs(cache_dir, exist_ok=True)
+ return cache_dir, os.path.join(cache_dir, "repos.json")
+
+
+def needs_rebuild(cache_file):
+ try:
+ age = time.time() - os.path.getmtime(cache_file)
+ return age > CACHE_MAX_AGE
+ except FileNotFoundError:
+ return True
+
+
+def has_github_remote(repo_path):
+ """Check if a git repo has a GitHub remote."""
+ try:
+ result = subprocess.run(
+ ["git", "-C", repo_path, "remote", "-v"],
+ capture_output=True, text=True, timeout=5,
+ )
+ return "github.com" in result.stdout
+ except (subprocess.TimeoutExpired, OSError):
+ return False
+
+
+def find_repos():
+ repos = []
+ for root, dirs, _files in os.walk(SRC_DIR):
+ # Skip worktree internal directories
+ if "worktrees" in root.split(os.sep):
+ dirs.clear()
+ continue
+ if ".git" in dirs or ".git" in _files:
+ if has_github_remote(root):
+ repos.append(root)
+ dirs.clear() # Don't descend into nested repos
+ repos.sort()
+ return [
+ {
+ "title": os.path.relpath(r, SRC_DIR),
+ "subtitle": r,
+ "arg": r,
+ }
+ for r in repos
+ ]
+
+
+def rebuild_cache(cache_dir, cache_file):
+ repos = find_repos()
+ fd, tmpfile = tempfile.mkstemp(suffix=".json", dir=cache_dir)
+ try:
+ with os.fdopen(fd, "w") as f:
+ json.dump(repos, f)
+ os.replace(tmpfile, cache_file)
+ except BaseException:
+ os.unlink(tmpfile)
+ raise
+
+
+def load_cache(cache_file):
+ try:
+ with open(cache_file) as f:
+ data = f.read()
+ if not data:
+ return []
+ return json.loads(data)
+ except (FileNotFoundError, json.JSONDecodeError):
+ return []
+
+
+def main():
+ cache_dir, cache_file = cache_paths()
+
+ if needs_rebuild(cache_file):
+ rebuild_cache(cache_dir, cache_file)
+
+ items = load_cache(cache_file)
+
+ query = " ".join(sys.argv[1:]).strip().lower()
+ if query:
+ items = [i for i in items if query in i["title"].lower()]
+
+ json.dump({"items": items}, sys.stdout)
+
+
+if __name__ == "__main__":
+ main()
dots/config/raffi/raffi.yaml
@@ -203,6 +203,11 @@ addons:
command: ~/.config/raffi/scripts/lazyworktree-raffi
icon: folder-git
action: "kitty --title Worktrees --directory {value} lazyworktree"
+ - name: Pull Requests
+ keyword: lpr
+ command: ~/.config/raffi/scripts/lazypr-raffi
+ icon: github
+ action: "kitty --title 'PRs' lazypr {value}"
- name: Shpool Sessions
keyword: sp
command: ~/.config/raffi/scripts/shpool-raffi