Commit a971262acd37

Vincent Demeester <vincent@sbr.pm>
2026-03-05 10:44:05
chore(raffi) add lazypr integration
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 965ca0f
Changed files (2)
dots
config
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