repr-cli 0.2.7__py3-none-any.whl → 0.2.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- repr/__init__.py +1 -1
- repr/cli.py +28 -0
- repr/updater.py +245 -0
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.8.dist-info}/METADATA +1 -1
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.8.dist-info}/RECORD +9 -8
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.8.dist-info}/WHEEL +0 -0
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.8.dist-info}/entry_points.txt +0 -0
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.8.dist-info}/licenses/LICENSE +0 -0
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.8.dist-info}/top_level.txt +0 -0
repr/__init__.py
CHANGED
repr/cli.py
CHANGED
|
@@ -2382,6 +2382,34 @@ def mode():
|
|
|
2382
2382
|
console.print(f" [{BRAND_MUTED}]✗[/] Publishing ({reason})")
|
|
2383
2383
|
|
|
2384
2384
|
|
|
2385
|
+
@app.command()
|
|
2386
|
+
def update(
|
|
2387
|
+
check: bool = typer.Option(False, "--check", "-c", help="Only check for updates, don't install"),
|
|
2388
|
+
force: bool = typer.Option(False, "--force", "-f", help="Force update even if already up to date"),
|
|
2389
|
+
):
|
|
2390
|
+
"""
|
|
2391
|
+
Update repr to the latest version.
|
|
2392
|
+
|
|
2393
|
+
Automatically detects installation method (Homebrew, pip, or binary)
|
|
2394
|
+
and updates accordingly.
|
|
2395
|
+
|
|
2396
|
+
Examples:
|
|
2397
|
+
repr update # Update to latest version
|
|
2398
|
+
repr update --check # Just check if update available
|
|
2399
|
+
"""
|
|
2400
|
+
from .updater import check_for_update, perform_update
|
|
2401
|
+
|
|
2402
|
+
if check:
|
|
2403
|
+
new_version = check_for_update()
|
|
2404
|
+
if new_version:
|
|
2405
|
+
print_info(f"New version available: v{new_version}")
|
|
2406
|
+
print_info("Run 'repr update' to install")
|
|
2407
|
+
else:
|
|
2408
|
+
print_success(f"Already up to date (v{__version__})")
|
|
2409
|
+
else:
|
|
2410
|
+
perform_update(force=force)
|
|
2411
|
+
|
|
2412
|
+
|
|
2385
2413
|
@app.command()
|
|
2386
2414
|
def doctor():
|
|
2387
2415
|
"""
|
repr/updater.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-update functionality for repr CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tarfile
|
|
11
|
+
import tempfile
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, Tuple
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
17
|
+
from . import __version__
|
|
18
|
+
from .ui import console, print_success, print_error, print_warning, print_info
|
|
19
|
+
|
|
20
|
+
GITHUB_REPO = "repr-app/cli"
|
|
21
|
+
GITHUB_API_URL = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_version(version: str) -> Tuple[int, ...]:
|
|
25
|
+
"""Parse version string into tuple for comparison."""
|
|
26
|
+
# Remove 'v' prefix if present
|
|
27
|
+
version = version.lstrip('v')
|
|
28
|
+
try:
|
|
29
|
+
return tuple(int(x) for x in version.split('.'))
|
|
30
|
+
except ValueError:
|
|
31
|
+
return (0, 0, 0)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_latest_version() -> Optional[dict]:
|
|
35
|
+
"""Fetch latest release info from GitHub."""
|
|
36
|
+
try:
|
|
37
|
+
with httpx.Client(timeout=10) as client:
|
|
38
|
+
response = client.get(GITHUB_API_URL)
|
|
39
|
+
response.raise_for_status()
|
|
40
|
+
return response.json()
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print_error(f"Failed to check for updates: {e}")
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def check_for_update() -> Optional[str]:
|
|
47
|
+
"""
|
|
48
|
+
Check if a newer version is available.
|
|
49
|
+
Returns the new version string if available, None otherwise.
|
|
50
|
+
"""
|
|
51
|
+
release = get_latest_version()
|
|
52
|
+
if not release:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
latest_version = release.get("tag_name", "").lstrip('v')
|
|
56
|
+
current = parse_version(__version__)
|
|
57
|
+
latest = parse_version(latest_version)
|
|
58
|
+
|
|
59
|
+
if latest > current:
|
|
60
|
+
return latest_version
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_installation_method() -> str:
|
|
65
|
+
"""Detect how repr was installed."""
|
|
66
|
+
executable = sys.executable
|
|
67
|
+
repr_path = shutil.which("repr")
|
|
68
|
+
|
|
69
|
+
if repr_path:
|
|
70
|
+
# Check if installed via Homebrew
|
|
71
|
+
if "/homebrew/" in repr_path.lower() or "/cellar/" in repr_path.lower():
|
|
72
|
+
return "homebrew"
|
|
73
|
+
|
|
74
|
+
# Check if it's a standalone binary (PyInstaller)
|
|
75
|
+
if getattr(sys, 'frozen', False):
|
|
76
|
+
return "binary"
|
|
77
|
+
|
|
78
|
+
# Check if installed via pip
|
|
79
|
+
try:
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
[sys.executable, "-m", "pip", "show", "repr-cli"],
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True
|
|
84
|
+
)
|
|
85
|
+
if result.returncode == 0:
|
|
86
|
+
return "pip"
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
return "unknown"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def update_via_homebrew() -> bool:
|
|
94
|
+
"""Update using Homebrew."""
|
|
95
|
+
print_info("Updating via Homebrew...")
|
|
96
|
+
try:
|
|
97
|
+
subprocess.run(["brew", "update"], check=True)
|
|
98
|
+
subprocess.run(["brew", "upgrade", "repr"], check=True)
|
|
99
|
+
return True
|
|
100
|
+
except subprocess.CalledProcessError as e:
|
|
101
|
+
print_error(f"Homebrew update failed: {e}")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def update_via_pip() -> bool:
|
|
106
|
+
"""Update using pip."""
|
|
107
|
+
print_info("Updating via pip...")
|
|
108
|
+
try:
|
|
109
|
+
subprocess.run(
|
|
110
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "repr-cli"],
|
|
111
|
+
check=True
|
|
112
|
+
)
|
|
113
|
+
return True
|
|
114
|
+
except subprocess.CalledProcessError as e:
|
|
115
|
+
print_error(f"pip update failed: {e}")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def update_via_binary(release: dict) -> bool:
|
|
120
|
+
"""Update standalone binary by downloading from GitHub releases."""
|
|
121
|
+
system = platform.system().lower()
|
|
122
|
+
|
|
123
|
+
# Determine asset name
|
|
124
|
+
if system == "darwin":
|
|
125
|
+
asset_name = "repr-macos.tar.gz"
|
|
126
|
+
elif system == "linux":
|
|
127
|
+
asset_name = "repr-linux.tar.gz"
|
|
128
|
+
elif system == "windows":
|
|
129
|
+
asset_name = "repr-windows.exe"
|
|
130
|
+
else:
|
|
131
|
+
print_error(f"Unsupported platform: {system}")
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
# Find the asset URL
|
|
135
|
+
asset_url = None
|
|
136
|
+
for asset in release.get("assets", []):
|
|
137
|
+
if asset["name"] == asset_name:
|
|
138
|
+
asset_url = asset["browser_download_url"]
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
if not asset_url:
|
|
142
|
+
print_error(f"Could not find {asset_name} in release assets")
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
print_info(f"Downloading {asset_name}...")
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
# Get current executable path
|
|
149
|
+
current_exe = shutil.which("repr") or sys.argv[0]
|
|
150
|
+
current_exe = Path(current_exe).resolve()
|
|
151
|
+
|
|
152
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
153
|
+
tmpdir = Path(tmpdir)
|
|
154
|
+
|
|
155
|
+
# Download the asset
|
|
156
|
+
with httpx.Client(timeout=60, follow_redirects=True) as client:
|
|
157
|
+
response = client.get(asset_url)
|
|
158
|
+
response.raise_for_status()
|
|
159
|
+
|
|
160
|
+
download_path = tmpdir / asset_name
|
|
161
|
+
download_path.write_bytes(response.content)
|
|
162
|
+
|
|
163
|
+
# Extract if it's a tarball
|
|
164
|
+
if asset_name.endswith(".tar.gz"):
|
|
165
|
+
with tarfile.open(download_path, "r:gz") as tar:
|
|
166
|
+
tar.extractall(tmpdir)
|
|
167
|
+
new_exe = tmpdir / "repr"
|
|
168
|
+
else:
|
|
169
|
+
new_exe = download_path
|
|
170
|
+
|
|
171
|
+
# Make executable
|
|
172
|
+
new_exe.chmod(0o755)
|
|
173
|
+
|
|
174
|
+
# Replace current executable
|
|
175
|
+
backup_path = current_exe.with_suffix(".old")
|
|
176
|
+
|
|
177
|
+
# Backup current
|
|
178
|
+
if current_exe.exists():
|
|
179
|
+
shutil.move(str(current_exe), str(backup_path))
|
|
180
|
+
|
|
181
|
+
# Install new
|
|
182
|
+
try:
|
|
183
|
+
shutil.move(str(new_exe), str(current_exe))
|
|
184
|
+
# Remove backup on success
|
|
185
|
+
if backup_path.exists():
|
|
186
|
+
backup_path.unlink()
|
|
187
|
+
except Exception:
|
|
188
|
+
# Restore backup on failure
|
|
189
|
+
if backup_path.exists():
|
|
190
|
+
shutil.move(str(backup_path), str(current_exe))
|
|
191
|
+
raise
|
|
192
|
+
|
|
193
|
+
return True
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print_error(f"Binary update failed: {e}")
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def perform_update(force: bool = False) -> bool:
|
|
201
|
+
"""
|
|
202
|
+
Check for and perform update.
|
|
203
|
+
Returns True if update was successful or not needed.
|
|
204
|
+
"""
|
|
205
|
+
print_info(f"Current version: {__version__}")
|
|
206
|
+
|
|
207
|
+
release = get_latest_version()
|
|
208
|
+
if not release:
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
latest_version = release.get("tag_name", "").lstrip('v')
|
|
212
|
+
current = parse_version(__version__)
|
|
213
|
+
latest = parse_version(latest_version)
|
|
214
|
+
|
|
215
|
+
if latest <= current and not force:
|
|
216
|
+
print_success(f"Already up to date (v{__version__})")
|
|
217
|
+
return True
|
|
218
|
+
|
|
219
|
+
print_info(f"New version available: v{latest_version}")
|
|
220
|
+
|
|
221
|
+
method = get_installation_method()
|
|
222
|
+
print_info(f"Installation method: {method}")
|
|
223
|
+
|
|
224
|
+
success = False
|
|
225
|
+
|
|
226
|
+
if method == "homebrew":
|
|
227
|
+
success = update_via_homebrew()
|
|
228
|
+
elif method == "pip":
|
|
229
|
+
success = update_via_pip()
|
|
230
|
+
elif method == "binary":
|
|
231
|
+
success = update_via_binary(release)
|
|
232
|
+
else:
|
|
233
|
+
print_warning("Could not detect installation method.")
|
|
234
|
+
print_info("Try one of these manually:")
|
|
235
|
+
console.print(" • brew upgrade repr")
|
|
236
|
+
console.print(" • pip install --upgrade repr-cli")
|
|
237
|
+
console.print(f" • Download from https://github.com/{GITHUB_REPO}/releases/latest")
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
if success:
|
|
241
|
+
print_success(f"Updated to v{latest_version}")
|
|
242
|
+
print_info("Restart your terminal or run 'repr -v' to verify")
|
|
243
|
+
|
|
244
|
+
return success
|
|
245
|
+
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
repr/__init__.py,sha256=
|
|
1
|
+
repr/__init__.py,sha256=jraImidqaPxv03Uy76zPtnAcNnOl5KLZSXYBzxI85BI,446
|
|
2
2
|
repr/__main__.py,sha256=M0ECtxOrmmYoYrYV5XI9UhDnOjWThxn0-PPysKs3RT0,127
|
|
3
3
|
repr/api.py,sha256=Rr6MEUkjf7LJ6TcxbdVstfpUM_mDpTKhllbFwy9jK2w,11893
|
|
4
4
|
repr/auth.py,sha256=-tqd2MMgFlowbhAqLHeSnVpDBkintkZ4kmPDZmczQFU,11682
|
|
5
|
-
repr/cli.py,sha256=
|
|
5
|
+
repr/cli.py,sha256=xCz40ZRx_sOznTxS7GP4m0aArN7d7c4QCpFLh2t_FYU,78218
|
|
6
6
|
repr/config.py,sha256=GZf5ucrBFIfOo9UtKE-DAZ9Ns1suAKG0jvUAY64oGIc,30601
|
|
7
7
|
repr/discovery.py,sha256=2RYmJleqV7TbxIMMYP2izkEBUeKH7U1F-U4KAUlUNww,14816
|
|
8
8
|
repr/doctor.py,sha256=6cI21xXIlTNRzHi2fRfHpm__erO8jBZc6vge8-29ip4,13404
|
|
@@ -17,9 +17,10 @@ repr/telemetry.py,sha256=7ANJJUB4Dd7A_HFVPqc92Gy77ruREzlmgayFQkwuC9s,6961
|
|
|
17
17
|
repr/templates.py,sha256=RQl7nUfy8IK6QFKzgpcebkBbQH0E_brbYh83pzym1TM,6530
|
|
18
18
|
repr/tools.py,sha256=QoGeti5Sye2wVuE-7UPxd_TDNXoen-xYfsFoT9rYRPs,20737
|
|
19
19
|
repr/ui.py,sha256=5jycUT-5Q0az4FFUzgarI8CfVAEEUPSEsT24Fad2kG8,3994
|
|
20
|
-
|
|
21
|
-
repr_cli-0.2.
|
|
22
|
-
repr_cli-0.2.
|
|
23
|
-
repr_cli-0.2.
|
|
24
|
-
repr_cli-0.2.
|
|
25
|
-
repr_cli-0.2.
|
|
20
|
+
repr/updater.py,sha256=E2ZRfeQxA4_UrWmIphJkafrUHU2UGUpfINNVLuTIcfI,7296
|
|
21
|
+
repr_cli-0.2.8.dist-info/licenses/LICENSE,sha256=tI16Ry3IQhjsde6weJ_in6czzWW2EF4Chz1uicyDLAA,1061
|
|
22
|
+
repr_cli-0.2.8.dist-info/METADATA,sha256=5YVmEcJf4Dt2OfAxaIND95w-wkZ-XBhddndijKFjTfw,10898
|
|
23
|
+
repr_cli-0.2.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
repr_cli-0.2.8.dist-info/entry_points.txt,sha256=SJoKgNB-fRy6O2T_lztFr9T3ND_BQl0ijWxNW-J7dUU,38
|
|
25
|
+
repr_cli-0.2.8.dist-info/top_level.txt,sha256=LNgPqdJPQnlicRve7uzI4a6rEUdcxHrNkUq_2w7eeiA,5
|
|
26
|
+
repr_cli-0.2.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|