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/doctor.py CHANGED
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
- # Format commits for the prompt
143
- commits_text = []
144
- for commit in commits:
145
- commit_text = f"""
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
- commit_text += f"\n {change_type}: {file_info['path']}"
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
- if file_info['diff']:
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
- commits_text.append(commit_text)
168
-
169
- commits_formatted = "\n\n---\n".join(commits_text)
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
- user_prompt = f"""Analyze commits batch {batch_num}/{total_batches} and extract technical accomplishments:
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/telemetry.py CHANGED
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
- lines.append(f" Files: {', '.join(files[:5])}")
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.7
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 since '2 weeks ago'` and export. Stakeholders love it."*
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 week
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 week
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 week` / `repr stories` | Pure local operations. |
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,,