repr-cli 0.2.16__py3-none-any.whl → 0.2.18__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/api.py +363 -62
- repr/auth.py +47 -38
- repr/change_synthesis.py +478 -0
- repr/cli.py +4306 -364
- repr/config.py +119 -11
- repr/configure.py +889 -0
- repr/cron.py +419 -0
- repr/dashboard/__init__.py +9 -0
- repr/dashboard/build.py +126 -0
- repr/dashboard/dist/assets/index-B-aCjaCw.js +384 -0
- repr/dashboard/dist/assets/index-BYFVbEev.css +1 -0
- repr/dashboard/dist/assets/index-BrrhyJFO.css +1 -0
- repr/dashboard/dist/assets/index-C7Gzxc4f.js +384 -0
- repr/dashboard/dist/assets/index-CQdMXo6g.js +391 -0
- repr/dashboard/dist/assets/index-CcEg74ts.js +270 -0
- repr/dashboard/dist/assets/index-Cerc-iA_.js +377 -0
- repr/dashboard/dist/assets/index-CjVcBW2L.css +1 -0
- repr/dashboard/dist/assets/index-Cs8ofFGd.js +384 -0
- repr/dashboard/dist/assets/index-Dfl3mR5E.js +377 -0
- repr/dashboard/dist/assets/index-DwN0SeMc.css +1 -0
- repr/dashboard/dist/assets/index-YFch_e0S.js +384 -0
- repr/dashboard/dist/favicon.svg +4 -0
- repr/dashboard/dist/index.html +14 -0
- repr/dashboard/manager.py +234 -0
- repr/dashboard/server.py +1489 -0
- repr/db.py +980 -0
- repr/hooks.py +3 -2
- repr/loaders/__init__.py +22 -0
- repr/loaders/base.py +156 -0
- repr/loaders/claude_code.py +287 -0
- repr/loaders/clawdbot.py +313 -0
- repr/loaders/gemini_antigravity.py +381 -0
- repr/mcp_server.py +1196 -0
- repr/models.py +503 -0
- repr/openai_analysis.py +25 -0
- repr/session_extractor.py +481 -0
- repr/storage.py +328 -0
- repr/story_synthesis.py +1296 -0
- repr/templates.py +68 -4
- repr/timeline.py +710 -0
- repr/tools.py +17 -8
- {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/METADATA +48 -10
- repr_cli-0.2.18.dist-info/RECORD +58 -0
- {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/WHEEL +1 -1
- {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/entry_points.txt +1 -0
- repr_cli-0.2.16.dist-info/RECORD +0 -26
- {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/licenses/LICENSE +0 -0
- {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.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
|
-
|
|
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 =
|
|
78
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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)
|