2323import subprocess
2424import sys
2525import time
26+ import threading
2627import urllib .error
2728import urllib .request
2829from pathlib import Path
@@ -80,6 +81,87 @@ def load_config() -> dict:
8081 return config
8182
8283
84+ # ---------------------------------------------------------------------------
85+ # Update check
86+ # ---------------------------------------------------------------------------
87+
88+ UPDATE_CHECK_INTERVAL = 3600 # seconds between PyPI checks
89+
90+
91+ def _update_cache_path () -> Path :
92+ cache_dir = Path (os .environ .get ("XDG_CACHE_HOME" ) or Path .home () / ".cache" )
93+ return cache_dir / "rpr" / "update-check.json"
94+
95+
96+ def _version_tuple (v : str ) -> tuple [int , ...]:
97+ try :
98+ return tuple (int (x ) for x in v .split ("." ))
99+ except (ValueError , AttributeError ):
100+ return (0 ,)
101+
102+
103+ def check_for_update () -> str | None :
104+ """Check PyPI for a newer version. Returns a notification string or None.
105+
106+ Results are cached for UPDATE_CHECK_INTERVAL seconds so most runs
107+ hit a local file instead of the network.
108+ """
109+ from rpr import __version__
110+
111+ cache_path = _update_cache_path ()
112+ latest = None
113+
114+ # Use cached result if fresh enough
115+ try :
116+ if cache_path .exists ():
117+ cache = json .loads (cache_path .read_text ())
118+ if time .time () - cache .get ("checked_at" , 0 ) < UPDATE_CHECK_INTERVAL :
119+ latest = cache .get ("latest" )
120+ if latest and _version_tuple (latest ) > _version_tuple (__version__ ):
121+ return _update_msg (__version__ , latest )
122+ return None
123+ except (json .JSONDecodeError , OSError ):
124+ pass
125+
126+ # Fetch from PyPI
127+ try :
128+ req = urllib .request .Request (
129+ "https://pypi.org/pypi/rpr/json" ,
130+ headers = {"Accept" : "application/json" },
131+ )
132+ with urllib .request .urlopen (req , timeout = 5 ) as resp :
133+ data = json .loads (resp .read ().decode ("utf-8" ))
134+ latest = data ["info" ]["version" ]
135+ except Exception :
136+ return None
137+
138+ # Cache the result
139+ try :
140+ cache_path .parent .mkdir (parents = True , exist_ok = True )
141+ cache_path .write_text (json .dumps ({
142+ "checked_at" : time .time (),
143+ "latest" : latest ,
144+ }))
145+ except OSError :
146+ pass
147+
148+ if latest and _version_tuple (latest ) > _version_tuple (__version__ ):
149+ return _update_msg (__version__ , latest )
150+ return None
151+
152+
153+ def _update_msg (current : str , latest : str ) -> str :
154+ line1 = f" Update available: { current } → { latest } "
155+ line2 = " pip install -U rpr "
156+ width = max (len (line1 ), len (line2 ))
157+ return (
158+ f"\n ╭{ '─' * width } ╮\n "
159+ f"│{ line1 :<{width }} │\n "
160+ f"│{ line2 :<{width }} │\n "
161+ f"╰{ '─' * width } ╯"
162+ )
163+
164+
83165# ---------------------------------------------------------------------------
84166# Review depth modes
85167# ---------------------------------------------------------------------------
@@ -791,6 +873,15 @@ def main():
791873 args = parser .parse_args ()
792874 config = load_config ()
793875
876+ # Start update check in background (non-blocking)
877+ update_result : list [str | None ] = [None ]
878+
879+ def _bg_update_check ():
880+ update_result [0 ] = check_for_update ()
881+
882+ update_thread = threading .Thread (target = _bg_update_check , daemon = True )
883+ update_thread .start ()
884+
794885 if args .model :
795886 config ["model" ] = args .model
796887
@@ -931,6 +1022,9 @@ def main():
9311022 print (f"\n 📄 { c ['path' ]} :{ c ['line' ]} " )
9321023 print (f" { c ['body' ]} " )
9331024 print ()
1025+ update_thread .join (timeout = 2 )
1026+ if update_result [0 ]:
1027+ print (update_result [0 ], file = sys .stderr )
9341028 return
9351029
9361030 if args .comment_only :
@@ -948,6 +1042,11 @@ def main():
9481042
9491043 print (f"🔗 { pr_info .get ('url' , '' )} " , file = sys .stderr )
9501044
1045+ # Show update notification (if check finished in time)
1046+ update_thread .join (timeout = 2 )
1047+ if update_result [0 ]:
1048+ print (update_result [0 ], file = sys .stderr )
1049+
9511050
9521051if __name__ == "__main__" :
9531052 main ()
0 commit comments