repr-cli 0.2.7__py3-none-any.whl → 0.2.9__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/__main__.py +37 -0
- repr/cli.py +378 -218
- repr/doctor.py +37 -0
- repr/llm.py +37 -0
- repr/openai_analysis.py +32 -23
- repr/privacy.py +37 -0
- repr/telemetry.py +37 -0
- repr/templates.py +6 -1
- repr/ui.py +37 -0
- repr/updater.py +282 -0
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.9.dist-info}/METADATA +19 -5
- repr_cli-0.2.9.dist-info/RECORD +26 -0
- repr_cli-0.2.7.dist-info/RECORD +0 -25
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.9.dist-info}/WHEEL +0 -0
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.9.dist-info}/entry_points.txt +0 -0
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.9.dist-info}/licenses/LICENSE +0 -0
- {repr_cli-0.2.7.dist-info → repr_cli-0.2.9.dist-info}/top_level.txt +0 -0
repr/doctor.py
CHANGED
|
@@ -456,3 +456,40 @@ def run_all_checks() -> DoctorReport:
|
|
|
456
456
|
recommendations=recommendations,
|
|
457
457
|
)
|
|
458
458
|
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
|
repr/llm.py
CHANGED
|
@@ -504,3 +504,40 @@ def get_effective_llm_mode() -> tuple[str, dict[str, Any]]:
|
|
|
504
504
|
"model": llm_config.get("local_model"),
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
|
repr/openai_analysis.py
CHANGED
|
@@ -122,6 +122,8 @@ async def extract_commit_batch(
|
|
|
122
122
|
batch_num: int,
|
|
123
123
|
total_batches: int,
|
|
124
124
|
model: str = None,
|
|
125
|
+
system_prompt: str = None,
|
|
126
|
+
user_prompt: str = None,
|
|
125
127
|
) -> str:
|
|
126
128
|
"""
|
|
127
129
|
Extraction phase: Extract accomplishments from a batch of commits.
|
|
@@ -132,6 +134,8 @@ async def extract_commit_batch(
|
|
|
132
134
|
batch_num: Current batch number (for context)
|
|
133
135
|
total_batches: Total number of batches
|
|
134
136
|
model: Model name to use (defaults to stored config or DEFAULT_EXTRACTION_MODEL)
|
|
137
|
+
system_prompt: Custom system prompt (optional, uses default if not provided)
|
|
138
|
+
user_prompt: Custom user prompt (optional, uses default if not provided)
|
|
135
139
|
|
|
136
140
|
Returns:
|
|
137
141
|
Summary of technical accomplishments in this batch
|
|
@@ -139,36 +143,40 @@ async def extract_commit_batch(
|
|
|
139
143
|
if not model:
|
|
140
144
|
llm_config = get_llm_config()
|
|
141
145
|
model = llm_config.get("extraction_model") or DEFAULT_EXTRACTION_MODEL
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
|
|
147
|
+
# Use provided prompts or build defaults
|
|
148
|
+
if not system_prompt or not user_prompt:
|
|
149
|
+
# Format commits for the prompt
|
|
150
|
+
commits_text = []
|
|
151
|
+
for commit in commits:
|
|
152
|
+
commit_text = f"""
|
|
146
153
|
Commit: {commit['sha']}
|
|
147
154
|
Date: {commit['date']}
|
|
148
155
|
Message: {commit['message']}
|
|
149
156
|
|
|
150
157
|
Files changed:"""
|
|
151
|
-
|
|
152
|
-
for file_info in commit['files'][:10]: # Limit files per commit
|
|
153
|
-
change_type = {
|
|
154
|
-
'A': 'Added',
|
|
155
|
-
'D': 'Deleted',
|
|
156
|
-
'M': 'Modified',
|
|
157
|
-
'R': 'Renamed'
|
|
158
|
-
}.get(file_info['change_type'], 'Changed')
|
|
159
158
|
|
|
160
|
-
|
|
159
|
+
for file_info in commit['files'][:10]: # Limit files per commit
|
|
160
|
+
change_type = {
|
|
161
|
+
'A': 'Added',
|
|
162
|
+
'D': 'Deleted',
|
|
163
|
+
'M': 'Modified',
|
|
164
|
+
'R': 'Renamed'
|
|
165
|
+
}.get(file_info['change_type'], 'Changed')
|
|
166
|
+
|
|
167
|
+
commit_text += f"\n {change_type}: {file_info['path']}"
|
|
168
|
+
|
|
169
|
+
if file_info['diff']:
|
|
170
|
+
# Truncate diff if too long (for token management)
|
|
171
|
+
diff = file_info['diff'][:2000]
|
|
172
|
+
commit_text += f"\n```diff\n{diff}\n```"
|
|
161
173
|
|
|
162
|
-
|
|
163
|
-
# Truncate diff if too long (for token management)
|
|
164
|
-
diff = file_info['diff'][:2000]
|
|
165
|
-
commit_text += f"\n```diff\n{diff}\n```"
|
|
174
|
+
commits_text.append(commit_text)
|
|
166
175
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
system_prompt = """You are analyzing a developer's actual code commits to extract specific technical accomplishments WITH the reasoning behind them.
|
|
176
|
+
commits_formatted = "\n\n---\n".join(commits_text)
|
|
177
|
+
|
|
178
|
+
if not system_prompt:
|
|
179
|
+
system_prompt = """You are analyzing a developer's actual code commits to extract specific technical accomplishments WITH the reasoning behind them.
|
|
172
180
|
|
|
173
181
|
Your job: Read the commit messages and diffs, then list CONCRETE technical accomplishments with SPECIFIC details AND infer WHY those decisions were made.
|
|
174
182
|
|
|
@@ -194,7 +202,8 @@ What NOT to do:
|
|
|
194
202
|
- Don't include process/methodology unless there's evidence
|
|
195
203
|
- Don't fabricate motivations that aren't supported by the code/commits"""
|
|
196
204
|
|
|
197
|
-
|
|
205
|
+
if not user_prompt:
|
|
206
|
+
user_prompt = f"""Analyze commits batch {batch_num}/{total_batches} and extract technical accomplishments:
|
|
198
207
|
|
|
199
208
|
{commits_formatted}
|
|
200
209
|
|
repr/privacy.py
CHANGED
|
@@ -331,3 +331,40 @@ def clear_audit_log() -> int:
|
|
|
331
331
|
|
|
332
332
|
return count
|
|
333
333
|
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
|
repr/telemetry.py
CHANGED
|
@@ -277,3 +277,40 @@ def get_pending_events() -> list[dict[str, Any]]:
|
|
|
277
277
|
"""
|
|
278
278
|
return _load_queue()
|
|
279
279
|
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
repr/templates.py
CHANGED
|
@@ -162,7 +162,12 @@ def format_commits_for_prompt(commits: list[dict[str, Any]]) -> str:
|
|
|
162
162
|
|
|
163
163
|
lines.append(f"- [{sha}] {msg}")
|
|
164
164
|
if files:
|
|
165
|
-
|
|
165
|
+
# Handle files as either list of dicts or list of strings
|
|
166
|
+
file_names = [
|
|
167
|
+
f["path"] if isinstance(f, dict) else f
|
|
168
|
+
for f in files[:5]
|
|
169
|
+
]
|
|
170
|
+
lines.append(f" Files: {', '.join(file_names)}")
|
|
166
171
|
if len(files) > 5:
|
|
167
172
|
lines.append(f" ... and {len(files) - 5} more files")
|
|
168
173
|
if c.get("insertions") or c.get("deletions"):
|
repr/ui.py
CHANGED
|
@@ -143,3 +143,40 @@ def confirm(message: str, default: bool = False) -> bool:
|
|
|
143
143
|
"""Prompt for confirmation."""
|
|
144
144
|
return Confirm.ask(message, default=default)
|
|
145
145
|
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
repr/updater.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
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
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repr-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: A beautiful, privacy-first CLI that analyzes your code repositories and generates a compelling developer profile
|
|
5
5
|
Author-email: Repr <hello@repr.dev>
|
|
6
6
|
License: MIT License
|
|
@@ -71,7 +71,7 @@ Turn commits into professional narratives for interviews, performance reviews, a
|
|
|
71
71
|
> *"I used repr to prep for my Meta interview in 30 minutes. Turned 2 years of commits into 8 STAR-format stories. Nailed every behavioral question."*
|
|
72
72
|
> **— Sarah, Senior Backend Engineer**
|
|
73
73
|
|
|
74
|
-
> *"Our sprint demos went from chaos to polished in 5 minutes. Just run `repr
|
|
74
|
+
> *"Our sprint demos went from chaos to polished in 5 minutes. Just run `repr commits --days 14` and export. Stakeholders love it."*
|
|
75
75
|
> **— Marcus, Engineering Manager**
|
|
76
76
|
|
|
77
77
|
> *"I run repr in a fully air-gapped environment. Zero network calls, 100% local. It's the only tool I trust for this."*
|
|
@@ -167,7 +167,7 @@ For full step-by-step guides, see the [documentation](https://repr.dev/docs/cli/
|
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
169
|
repr init ~/code
|
|
170
|
-
repr
|
|
170
|
+
repr commits --days 7
|
|
171
171
|
repr generate --local
|
|
172
172
|
```
|
|
173
173
|
|
|
@@ -186,7 +186,7 @@ repr review
|
|
|
186
186
|
### Weekly reflection
|
|
187
187
|
|
|
188
188
|
```bash
|
|
189
|
-
repr
|
|
189
|
+
repr commits --days 7
|
|
190
190
|
repr generate --local
|
|
191
191
|
repr story edit <id>
|
|
192
192
|
repr story feature <id>
|
|
@@ -204,6 +204,20 @@ repr story view <id>
|
|
|
204
204
|
|
|
205
205
|
[Full guide →](https://repr.dev/docs/cli/workflows/interview-prep)
|
|
206
206
|
|
|
207
|
+
### Generate from a specific timeframe
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Last 30 days
|
|
211
|
+
repr generate --days 30 --local
|
|
212
|
+
|
|
213
|
+
# Since a specific date
|
|
214
|
+
repr generate --since 2024-01-01 --local
|
|
215
|
+
|
|
216
|
+
# Natural language dates
|
|
217
|
+
repr generate --since "2 weeks ago" --local
|
|
218
|
+
repr generate --since monday --local
|
|
219
|
+
```
|
|
220
|
+
|
|
207
221
|
### Publish your profile (optional)
|
|
208
222
|
|
|
209
223
|
```bash
|
|
@@ -274,7 +288,7 @@ repr llm use byok:openai
|
|
|
274
288
|
| **Local LLM** | `repr generate --local` | Talks only to your local endpoint. |
|
|
275
289
|
| **BYOK** | `repr llm add <provider>` | Calls your provider directly with your key. |
|
|
276
290
|
| **Cloud** | `repr generate --cloud` | Requires login; you initiate all network calls. |
|
|
277
|
-
| **Offline** | `repr
|
|
291
|
+
| **Offline** | `repr commits` / `repr stories` | Pure local operations. |
|
|
278
292
|
|
|
279
293
|
## Command help
|
|
280
294
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
repr/__init__.py,sha256=jraImidqaPxv03Uy76zPtnAcNnOl5KLZSXYBzxI85BI,446
|
|
2
|
+
repr/__main__.py,sha256=edYQ5TsuidoAKR1DSuTNcRKudj4lijHZGURb_CSem1M,164
|
|
3
|
+
repr/api.py,sha256=Rr6MEUkjf7LJ6TcxbdVstfpUM_mDpTKhllbFwy9jK2w,11893
|
|
4
|
+
repr/auth.py,sha256=-tqd2MMgFlowbhAqLHeSnVpDBkintkZ4kmPDZmczQFU,11682
|
|
5
|
+
repr/cli.py,sha256=jmIn86UxSAPLM7i2GWvmnoF2ewg_a69sQ3UGGxuc8e4,83784
|
|
6
|
+
repr/config.py,sha256=GZf5ucrBFIfOo9UtKE-DAZ9Ns1suAKG0jvUAY64oGIc,30601
|
|
7
|
+
repr/discovery.py,sha256=2RYmJleqV7TbxIMMYP2izkEBUeKH7U1F-U4KAUlUNww,14816
|
|
8
|
+
repr/doctor.py,sha256=cD-XLCVXfME0DsgOWU4VUOv28O4avjSQmMK6X_Kddyk,13441
|
|
9
|
+
repr/extractor.py,sha256=lGPN8gwTF_ZSezoQoPBMnf95nCJArGIteNiInfb39FM,10566
|
|
10
|
+
repr/hooks.py,sha256=DRpVXVv5Lesn9ARKHr-I91bUScab2It2TPjdwM38bT4,16864
|
|
11
|
+
repr/keychain.py,sha256=CpKU3tjFZVEPgiHiplSAtBQFDPA6qOSovv4IXXgJXbY,6957
|
|
12
|
+
repr/llm.py,sha256=kLcRq2wZTiGJKmkzUIBCpybD4S4efTGc87f_GL4IlDU,14635
|
|
13
|
+
repr/openai_analysis.py,sha256=KtluzntxRoBY_KylkMLPTb90UpV7fLJxDJ4UNOB5qwQ,26255
|
|
14
|
+
repr/privacy.py,sha256=r-HvQ4C56whI9Cbp4AHMwULvueBdYaO0pu3U1AoqB9M,9832
|
|
15
|
+
repr/storage.py,sha256=72nfFcR2Y98vpSjaO7zVHisq_Ln2UrHmGyDhEqEmDjU,14863
|
|
16
|
+
repr/telemetry.py,sha256=OR9w3v4VrVYu3C4Q0GQsX3-2JXSQvUhiYnkC6S5YOGc,6998
|
|
17
|
+
repr/templates.py,sha256=3YlhiUtnmsj6eQw3BPvvDty0-lf7oLxuYlbISE0KQrI,6733
|
|
18
|
+
repr/tools.py,sha256=QoGeti5Sye2wVuE-7UPxd_TDNXoen-xYfsFoT9rYRPs,20737
|
|
19
|
+
repr/ui.py,sha256=3Fc1ugwtfySIuB5LSE5R18tMHbz211TZNolVDlwKDAU,4031
|
|
20
|
+
repr/updater.py,sha256=sn3VEwtPkn1LLla2hGO53EmrY_ToRfMlDbFQawKhSZ4,7333
|
|
21
|
+
repr_cli-0.2.9.dist-info/licenses/LICENSE,sha256=tI16Ry3IQhjsde6weJ_in6czzWW2EF4Chz1uicyDLAA,1061
|
|
22
|
+
repr_cli-0.2.9.dist-info/METADATA,sha256=KA77WQLqSuOZIp9LMAVD24ev2aSAUZOt9glkbcNVYXk,11196
|
|
23
|
+
repr_cli-0.2.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
repr_cli-0.2.9.dist-info/entry_points.txt,sha256=SJoKgNB-fRy6O2T_lztFr9T3ND_BQl0ijWxNW-J7dUU,38
|
|
25
|
+
repr_cli-0.2.9.dist-info/top_level.txt,sha256=LNgPqdJPQnlicRve7uzI4a6rEUdcxHrNkUq_2w7eeiA,5
|
|
26
|
+
repr_cli-0.2.9.dist-info/RECORD,,
|