repr-cli 0.2.15__py3-none-any.whl → 0.2.17__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.
Files changed (43) hide show
  1. repr/__init__.py +1 -1
  2. repr/api.py +363 -62
  3. repr/auth.py +47 -38
  4. repr/change_synthesis.py +478 -0
  5. repr/cli.py +4103 -267
  6. repr/config.py +119 -11
  7. repr/configure.py +889 -0
  8. repr/cron.py +419 -0
  9. repr/dashboard/__init__.py +9 -0
  10. repr/dashboard/build.py +126 -0
  11. repr/dashboard/dist/assets/index-BYFVbEev.css +1 -0
  12. repr/dashboard/dist/assets/index-BrrhyJFO.css +1 -0
  13. repr/dashboard/dist/assets/index-CcEg74ts.js +270 -0
  14. repr/dashboard/dist/assets/index-Cerc-iA_.js +377 -0
  15. repr/dashboard/dist/assets/index-CjVcBW2L.css +1 -0
  16. repr/dashboard/dist/assets/index-Dfl3mR5E.js +377 -0
  17. repr/dashboard/dist/favicon.svg +4 -0
  18. repr/dashboard/dist/index.html +14 -0
  19. repr/dashboard/manager.py +234 -0
  20. repr/dashboard/server.py +1298 -0
  21. repr/db.py +980 -0
  22. repr/hooks.py +3 -2
  23. repr/loaders/__init__.py +22 -0
  24. repr/loaders/base.py +156 -0
  25. repr/loaders/claude_code.py +287 -0
  26. repr/loaders/clawdbot.py +313 -0
  27. repr/loaders/gemini_antigravity.py +381 -0
  28. repr/mcp_server.py +1196 -0
  29. repr/models.py +503 -0
  30. repr/openai_analysis.py +25 -0
  31. repr/session_extractor.py +481 -0
  32. repr/storage.py +360 -0
  33. repr/story_synthesis.py +1296 -0
  34. repr/templates.py +68 -4
  35. repr/timeline.py +710 -0
  36. repr/tools.py +17 -8
  37. {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/METADATA +50 -10
  38. repr_cli-0.2.17.dist-info/RECORD +52 -0
  39. {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/WHEEL +1 -1
  40. {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/entry_points.txt +1 -0
  41. repr_cli-0.2.15.dist-info/RECORD +0 -26
  42. {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/licenses/LICENSE +0 -0
  43. {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/top_level.txt +0 -0
repr/auth.py CHANGED
@@ -54,6 +54,7 @@ class TokenResponse:
54
54
  access_token: str
55
55
  user_id: str
56
56
  email: str
57
+ username: str | None = None
57
58
  litellm_api_key: str | None = None # rf_* token for LLM proxy authentication
58
59
 
59
60
 
@@ -65,23 +66,26 @@ class AuthError(Exception):
65
66
  async def request_device_code() -> DeviceCodeResponse:
66
67
  """
67
68
  Request a new device code for authentication.
68
-
69
+
69
70
  Returns:
70
71
  DeviceCodeResponse with user code to display
71
-
72
+
72
73
  Raises:
73
74
  AuthError: If request fails
74
75
  """
75
- async with httpx.AsyncClient() as client:
76
+ url = _get_device_code_url()
77
+ print(f"[DEBUG] Requesting device code from: {url}")
78
+ # Use sync client to avoid event loop cleanup issues
79
+ with httpx.Client() as client:
76
80
  try:
77
- response = await client.post(
78
- _get_device_code_url(),
81
+ response = client.post(
82
+ url,
79
83
  json={"client_id": "repr-cli"},
80
84
  timeout=30,
81
85
  )
82
86
  response.raise_for_status()
83
87
  data = response.json()
84
-
88
+
85
89
  return DeviceCodeResponse(
86
90
  device_code=data["device_code"],
87
91
  user_code=data["user_code"],
@@ -98,23 +102,24 @@ async def request_device_code() -> DeviceCodeResponse:
98
102
  async def poll_for_token(device_code: str, interval: int = POLL_INTERVAL) -> TokenResponse:
99
103
  """
100
104
  Poll for access token after user authorizes.
101
-
105
+
102
106
  Args:
103
107
  device_code: The device code from initial request
104
108
  interval: Polling interval in seconds
105
-
109
+
106
110
  Returns:
107
111
  TokenResponse with access token
108
-
112
+
109
113
  Raises:
110
114
  AuthError: If polling fails or times out
111
115
  """
112
116
  start_time = time.time()
113
-
114
- async with httpx.AsyncClient() as client:
117
+
118
+ # Use sync client to avoid event loop cleanup issues
119
+ with httpx.Client() as client:
115
120
  while time.time() - start_time < MAX_POLL_TIME:
116
121
  try:
117
- response = await client.post(
122
+ response = client.post(
118
123
  _get_token_url(),
119
124
  json={
120
125
  "device_code": device_code,
@@ -124,20 +129,21 @@ async def poll_for_token(device_code: str, interval: int = POLL_INTERVAL) -> Tok
124
129
  },
125
130
  timeout=30,
126
131
  )
127
-
132
+
128
133
  if response.status_code == 200:
129
134
  data = response.json()
130
135
  return TokenResponse(
131
136
  access_token=data["access_token"],
132
137
  user_id=data["user_id"],
133
138
  email=data["email"],
139
+ username=data.get("username"),
134
140
  litellm_api_key=data.get("litellm_api_key"),
135
141
  )
136
-
142
+
137
143
  if response.status_code == 400:
138
144
  data = response.json()
139
145
  error = data.get("error", "unknown")
140
-
146
+
141
147
  if error == "authorization_pending":
142
148
  # User hasn't authorized yet, continue polling
143
149
  await asyncio.sleep(interval)
@@ -153,23 +159,23 @@ async def poll_for_token(device_code: str, interval: int = POLL_INTERVAL) -> Tok
153
159
  raise AuthError("Authorization denied by user.")
154
160
  else:
155
161
  raise AuthError(f"Authorization failed: {error}")
156
-
162
+
157
163
  response.raise_for_status()
158
-
164
+
159
165
  except httpx.RequestError as e:
160
166
  # Network error, retry
161
167
  await asyncio.sleep(interval)
162
168
  continue
163
-
169
+
164
170
  raise AuthError("Authorization timed out. Please try again.")
165
171
 
166
172
 
167
173
  def save_token(token_response: TokenResponse) -> None:
168
174
  """
169
175
  Save authentication token securely.
170
-
176
+
171
177
  Token is stored in OS keychain, config only stores a reference.
172
-
178
+
173
179
  Args:
174
180
  token_response: Token response from successful auth
175
181
  """
@@ -177,6 +183,7 @@ def save_token(token_response: TokenResponse) -> None:
177
183
  access_token=token_response.access_token,
178
184
  user_id=token_response.user_id,
179
185
  email=token_response.email,
186
+ username=token_response.username,
180
187
  litellm_api_key=token_response.litellm_api_key,
181
188
  )
182
189
 
@@ -297,34 +304,35 @@ class AuthFlow:
297
304
  async def run(self) -> TokenResponse | None:
298
305
  """
299
306
  Run the full authentication flow.
300
-
307
+
301
308
  Returns:
302
309
  TokenResponse if successful, None if cancelled
303
310
  """
304
311
  try:
305
312
  # Request device code
306
313
  device_code_response = await request_device_code()
307
-
314
+
308
315
  if self.on_code_received:
309
316
  self.on_code_received(device_code_response)
310
-
317
+
311
318
  # Poll for token
312
319
  start_time = time.time()
313
320
  interval = device_code_response.interval
314
-
315
- async with httpx.AsyncClient() as client:
321
+
322
+ # Use sync client to avoid event loop cleanup issues
323
+ with httpx.Client() as client:
316
324
  while not self._cancelled:
317
325
  elapsed = time.time() - start_time
318
326
  remaining = device_code_response.expires_in - elapsed
319
-
327
+
320
328
  if remaining <= 0:
321
329
  raise AuthError("Device code expired. Please try again.")
322
-
330
+
323
331
  if self.on_progress:
324
332
  self.on_progress(remaining)
325
-
333
+
326
334
  try:
327
- response = await client.post(
335
+ response = client.post(
328
336
  _get_token_url(),
329
337
  json={
330
338
  "device_code": device_code_response.device_code,
@@ -334,7 +342,7 @@ class AuthFlow:
334
342
  },
335
343
  timeout=30,
336
344
  )
337
-
345
+
338
346
  if response.status_code == 200:
339
347
  data = response.json()
340
348
  user_data = data.get("user", {})
@@ -342,19 +350,20 @@ class AuthFlow:
342
350
  access_token=data["access_token"],
343
351
  user_id=user_data.get("id", ""),
344
352
  email=user_data.get("email", ""),
353
+ username=user_data.get("username"),
345
354
  litellm_api_key=data.get("litellm_api_key"),
346
355
  )
347
356
  save_token(token)
348
-
357
+
349
358
  if self.on_success:
350
359
  self.on_success(token)
351
-
360
+
352
361
  return token
353
-
362
+
354
363
  if response.status_code == 400:
355
364
  data = response.json()
356
365
  error = data.get("error", "unknown")
357
-
366
+
358
367
  if error == "authorization_pending":
359
368
  await asyncio.sleep(interval)
360
369
  continue
@@ -366,15 +375,15 @@ class AuthFlow:
366
375
  raise AuthError("Device code expired. Please try again.")
367
376
  elif error == "access_denied":
368
377
  raise AuthError("Authorization denied by user.")
369
-
378
+
370
379
  except httpx.RequestError:
371
380
  await asyncio.sleep(interval)
372
381
  continue
373
-
382
+
374
383
  await asyncio.sleep(interval)
375
-
384
+
376
385
  return None # Cancelled
377
-
386
+
378
387
  except AuthError as e:
379
388
  if self.on_error:
380
389
  self.on_error(e)