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.
- repr/__init__.py +1 -1
- repr/api.py +363 -62
- repr/auth.py +47 -38
- repr/change_synthesis.py +478 -0
- repr/cli.py +4103 -267
- 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-BYFVbEev.css +1 -0
- repr/dashboard/dist/assets/index-BrrhyJFO.css +1 -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-Dfl3mR5E.js +377 -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 +1298 -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 +360 -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.15.dist-info → repr_cli-0.2.17.dist-info}/METADATA +50 -10
- repr_cli-0.2.17.dist-info/RECORD +52 -0
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/WHEEL +1 -1
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/entry_points.txt +1 -0
- repr_cli-0.2.15.dist-info/RECORD +0 -26
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/licenses/LICENSE +0 -0
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/top_level.txt +0 -0
repr/__init__.py
CHANGED
repr/api.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
REST API client for repr.dev endpoints.
|
|
3
|
+
|
|
4
|
+
Uses synchronous httpx.Client to avoid asyncio event loop cleanup issues
|
|
5
|
+
when called from sync contexts via asyncio.run().
|
|
3
6
|
"""
|
|
4
7
|
|
|
5
8
|
import hashlib
|
|
@@ -56,24 +59,25 @@ def compute_content_hash(content: str) -> str:
|
|
|
56
59
|
async def push_profile(content: str, profile_name: str, analyzed_repos: list[dict[str, Any] | str] | None = None) -> dict[str, Any]:
|
|
57
60
|
"""
|
|
58
61
|
Push a profile to repr.dev.
|
|
59
|
-
|
|
62
|
+
|
|
60
63
|
Args:
|
|
61
64
|
content: Markdown content of the profile
|
|
62
65
|
profile_name: Name/identifier of the profile
|
|
63
66
|
analyzed_repos: Optional list of repository metadata (dicts) or names (strings for backward compat)
|
|
64
|
-
|
|
67
|
+
|
|
65
68
|
Returns:
|
|
66
69
|
Response data with profile URL
|
|
67
|
-
|
|
70
|
+
|
|
68
71
|
Raises:
|
|
69
72
|
APIError: If upload fails
|
|
70
73
|
AuthError: If not authenticated
|
|
71
74
|
"""
|
|
72
|
-
|
|
75
|
+
# Use sync client to avoid event loop cleanup issues
|
|
76
|
+
with httpx.Client() as client:
|
|
73
77
|
try:
|
|
74
78
|
# Compute content hash
|
|
75
79
|
content_hash = compute_content_hash(content)
|
|
76
|
-
|
|
80
|
+
|
|
77
81
|
payload = {
|
|
78
82
|
"content": content,
|
|
79
83
|
"name": profile_name,
|
|
@@ -81,8 +85,8 @@ async def push_profile(content: str, profile_name: str, analyzed_repos: list[dic
|
|
|
81
85
|
}
|
|
82
86
|
if analyzed_repos is not None:
|
|
83
87
|
payload["analyzed_repos"] = analyzed_repos
|
|
84
|
-
|
|
85
|
-
response =
|
|
88
|
+
|
|
89
|
+
response = client.post(
|
|
86
90
|
_get_profile_url(),
|
|
87
91
|
headers=_get_headers(),
|
|
88
92
|
json=payload,
|
|
@@ -90,7 +94,7 @@ async def push_profile(content: str, profile_name: str, analyzed_repos: list[dic
|
|
|
90
94
|
)
|
|
91
95
|
response.raise_for_status()
|
|
92
96
|
return response.json()
|
|
93
|
-
|
|
97
|
+
|
|
94
98
|
except httpx.HTTPStatusError as e:
|
|
95
99
|
if e.response.status_code == 401:
|
|
96
100
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
@@ -105,28 +109,28 @@ async def push_profile(content: str, profile_name: str, analyzed_repos: list[dic
|
|
|
105
109
|
async def get_user_profile() -> dict[str, Any] | None:
|
|
106
110
|
"""
|
|
107
111
|
Get the user's current profile from the server.
|
|
108
|
-
|
|
112
|
+
|
|
109
113
|
Returns:
|
|
110
114
|
Profile data or None if not found
|
|
111
|
-
|
|
115
|
+
|
|
112
116
|
Raises:
|
|
113
117
|
APIError: If request fails
|
|
114
118
|
AuthError: If not authenticated
|
|
115
119
|
"""
|
|
116
|
-
|
|
120
|
+
with httpx.Client() as client:
|
|
117
121
|
try:
|
|
118
|
-
response =
|
|
122
|
+
response = client.get(
|
|
119
123
|
_get_profile_url(),
|
|
120
124
|
headers=_get_headers(),
|
|
121
125
|
timeout=30,
|
|
122
126
|
)
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
if response.status_code == 404:
|
|
125
129
|
return None
|
|
126
|
-
|
|
130
|
+
|
|
127
131
|
response.raise_for_status()
|
|
128
132
|
return response.json()
|
|
129
|
-
|
|
133
|
+
|
|
130
134
|
except httpx.HTTPStatusError as e:
|
|
131
135
|
if e.response.status_code == 401:
|
|
132
136
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
@@ -138,24 +142,24 @@ async def get_user_profile() -> dict[str, Any] | None:
|
|
|
138
142
|
async def get_user_info() -> dict[str, Any]:
|
|
139
143
|
"""
|
|
140
144
|
Get current user information.
|
|
141
|
-
|
|
145
|
+
|
|
142
146
|
Returns:
|
|
143
147
|
User info dict
|
|
144
|
-
|
|
148
|
+
|
|
145
149
|
Raises:
|
|
146
150
|
APIError: If request fails
|
|
147
151
|
AuthError: If not authenticated
|
|
148
152
|
"""
|
|
149
|
-
|
|
153
|
+
with httpx.Client() as client:
|
|
150
154
|
try:
|
|
151
|
-
response =
|
|
155
|
+
response = client.get(
|
|
152
156
|
_get_user_url(),
|
|
153
157
|
headers=_get_headers(),
|
|
154
158
|
timeout=30,
|
|
155
159
|
)
|
|
156
160
|
response.raise_for_status()
|
|
157
161
|
return response.json()
|
|
158
|
-
|
|
162
|
+
|
|
159
163
|
except httpx.HTTPStatusError as e:
|
|
160
164
|
if e.response.status_code == 401:
|
|
161
165
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
@@ -167,24 +171,24 @@ async def get_user_info() -> dict[str, Any]:
|
|
|
167
171
|
async def delete_profile() -> bool:
|
|
168
172
|
"""
|
|
169
173
|
Delete the user's profile from the server.
|
|
170
|
-
|
|
174
|
+
|
|
171
175
|
Returns:
|
|
172
176
|
True if deleted successfully
|
|
173
|
-
|
|
177
|
+
|
|
174
178
|
Raises:
|
|
175
179
|
APIError: If request fails
|
|
176
180
|
AuthError: If not authenticated
|
|
177
181
|
"""
|
|
178
|
-
|
|
182
|
+
with httpx.Client() as client:
|
|
179
183
|
try:
|
|
180
|
-
response =
|
|
184
|
+
response = client.delete(
|
|
181
185
|
_get_profile_url(),
|
|
182
186
|
headers=_get_headers(),
|
|
183
187
|
timeout=30,
|
|
184
188
|
)
|
|
185
189
|
response.raise_for_status()
|
|
186
190
|
return True
|
|
187
|
-
|
|
191
|
+
|
|
188
192
|
except httpx.HTTPStatusError as e:
|
|
189
193
|
if e.response.status_code == 401:
|
|
190
194
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
@@ -202,31 +206,31 @@ async def push_repo_profile(
|
|
|
202
206
|
) -> dict[str, Any]:
|
|
203
207
|
"""
|
|
204
208
|
Push a single repository profile to repr.dev.
|
|
205
|
-
|
|
209
|
+
|
|
206
210
|
Args:
|
|
207
211
|
content: Markdown content of the profile
|
|
208
212
|
repo_name: Name of the repository
|
|
209
213
|
repo_metadata: Repository metadata (commit_count, languages, etc.)
|
|
210
|
-
|
|
214
|
+
|
|
211
215
|
Returns:
|
|
212
216
|
Response data with profile URL
|
|
213
|
-
|
|
217
|
+
|
|
214
218
|
Raises:
|
|
215
219
|
APIError: If upload fails
|
|
216
220
|
AuthError: If not authenticated
|
|
217
221
|
"""
|
|
218
|
-
|
|
222
|
+
with httpx.Client() as client:
|
|
219
223
|
try:
|
|
220
224
|
content_hash = compute_content_hash(content)
|
|
221
|
-
|
|
225
|
+
|
|
222
226
|
payload = {
|
|
223
227
|
"repo_name": repo_name,
|
|
224
228
|
"content": content,
|
|
225
229
|
"content_hash": content_hash,
|
|
226
230
|
**repo_metadata,
|
|
227
231
|
}
|
|
228
|
-
|
|
229
|
-
response =
|
|
232
|
+
|
|
233
|
+
response = client.post(
|
|
230
234
|
_get_repo_profile_url(),
|
|
231
235
|
headers=_get_headers(),
|
|
232
236
|
json=payload,
|
|
@@ -234,7 +238,7 @@ async def push_repo_profile(
|
|
|
234
238
|
)
|
|
235
239
|
response.raise_for_status()
|
|
236
240
|
return response.json()
|
|
237
|
-
|
|
241
|
+
|
|
238
242
|
except httpx.HTTPStatusError as e:
|
|
239
243
|
if e.response.status_code == 401:
|
|
240
244
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
@@ -282,22 +286,22 @@ async def get_stories(
|
|
|
282
286
|
) -> dict[str, Any]:
|
|
283
287
|
"""
|
|
284
288
|
Get commit stories from repr.dev.
|
|
285
|
-
|
|
289
|
+
|
|
286
290
|
Args:
|
|
287
291
|
repo_name: Filter by repository name
|
|
288
292
|
since: Filter by date (ISO format or human-readable like "1 week ago")
|
|
289
293
|
technologies: Comma-separated list of technologies to filter by
|
|
290
294
|
limit: Maximum number of stories to return
|
|
291
295
|
offset: Pagination offset
|
|
292
|
-
|
|
296
|
+
|
|
293
297
|
Returns:
|
|
294
298
|
Dict with 'stories' list and 'total' count
|
|
295
|
-
|
|
299
|
+
|
|
296
300
|
Raises:
|
|
297
301
|
APIError: If request fails
|
|
298
302
|
AuthError: If not authenticated
|
|
299
303
|
"""
|
|
300
|
-
|
|
304
|
+
with httpx.Client() as client:
|
|
301
305
|
try:
|
|
302
306
|
params: dict[str, Any] = {
|
|
303
307
|
"limit": limit,
|
|
@@ -309,8 +313,8 @@ async def get_stories(
|
|
|
309
313
|
params["since"] = since
|
|
310
314
|
if technologies:
|
|
311
315
|
params["technologies"] = technologies
|
|
312
|
-
|
|
313
|
-
response =
|
|
316
|
+
|
|
317
|
+
response = client.get(
|
|
314
318
|
_get_stories_url(),
|
|
315
319
|
headers=_get_headers(),
|
|
316
320
|
params=params,
|
|
@@ -318,7 +322,7 @@ async def get_stories(
|
|
|
318
322
|
)
|
|
319
323
|
response.raise_for_status()
|
|
320
324
|
return response.json()
|
|
321
|
-
|
|
325
|
+
|
|
322
326
|
except httpx.HTTPStatusError as e:
|
|
323
327
|
if e.response.status_code == 401:
|
|
324
328
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
@@ -330,20 +334,20 @@ async def get_stories(
|
|
|
330
334
|
async def push_story(story_data: dict[str, Any]) -> dict[str, Any]:
|
|
331
335
|
"""
|
|
332
336
|
Push a commit story to repr.dev.
|
|
333
|
-
|
|
337
|
+
|
|
334
338
|
Args:
|
|
335
339
|
story_data: Story data including summary, technologies, repo info, etc.
|
|
336
|
-
|
|
340
|
+
|
|
337
341
|
Returns:
|
|
338
342
|
Created/updated story data
|
|
339
|
-
|
|
343
|
+
|
|
340
344
|
Raises:
|
|
341
345
|
APIError: If request fails
|
|
342
346
|
AuthError: If not authenticated
|
|
343
347
|
"""
|
|
344
|
-
|
|
348
|
+
with httpx.Client() as client:
|
|
345
349
|
try:
|
|
346
|
-
response =
|
|
350
|
+
response = client.post(
|
|
347
351
|
_get_stories_url(),
|
|
348
352
|
headers=_get_headers(),
|
|
349
353
|
json=story_data,
|
|
@@ -351,7 +355,7 @@ async def push_story(story_data: dict[str, Any]) -> dict[str, Any]:
|
|
|
351
355
|
)
|
|
352
356
|
response.raise_for_status()
|
|
353
357
|
return response.json()
|
|
354
|
-
|
|
358
|
+
|
|
355
359
|
except httpx.HTTPStatusError as e:
|
|
356
360
|
if e.response.status_code == 401:
|
|
357
361
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
@@ -366,15 +370,15 @@ BATCH_SIZE = 200 # Maximum stories per batch request
|
|
|
366
370
|
async def push_stories_batch(stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
367
371
|
"""
|
|
368
372
|
Push multiple stories to repr.dev in batches.
|
|
369
|
-
|
|
373
|
+
|
|
370
374
|
Stories are automatically chunked into batches of BATCH_SIZE (200).
|
|
371
|
-
|
|
375
|
+
|
|
372
376
|
Args:
|
|
373
377
|
stories: List of story data dicts, each including summary, content, repo info, etc.
|
|
374
|
-
|
|
378
|
+
|
|
375
379
|
Returns:
|
|
376
380
|
Dict with 'pushed' count, 'failed' count, and 'results' list
|
|
377
|
-
|
|
381
|
+
|
|
378
382
|
Raises:
|
|
379
383
|
APIError: If request fails
|
|
380
384
|
AuthError: If not authenticated
|
|
@@ -382,14 +386,14 @@ async def push_stories_batch(stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
382
386
|
all_results: list[dict[str, Any]] = []
|
|
383
387
|
total_pushed = 0
|
|
384
388
|
total_failed = 0
|
|
385
|
-
|
|
389
|
+
|
|
386
390
|
# Process in chunks of BATCH_SIZE
|
|
387
391
|
for i in range(0, len(stories), BATCH_SIZE):
|
|
388
392
|
chunk = stories[i:i + BATCH_SIZE]
|
|
389
|
-
|
|
390
|
-
|
|
393
|
+
|
|
394
|
+
with httpx.Client() as client:
|
|
391
395
|
try:
|
|
392
|
-
response =
|
|
396
|
+
response = client.post(
|
|
393
397
|
f"{_get_stories_url()}/batch",
|
|
394
398
|
headers=_get_headers(),
|
|
395
399
|
json={"stories": chunk},
|
|
@@ -397,18 +401,18 @@ async def push_stories_batch(stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
397
401
|
)
|
|
398
402
|
response.raise_for_status()
|
|
399
403
|
result = response.json()
|
|
400
|
-
|
|
404
|
+
|
|
401
405
|
total_pushed += result.get("pushed", 0)
|
|
402
406
|
total_failed += result.get("failed", 0)
|
|
403
407
|
all_results.extend(result.get("results", []))
|
|
404
|
-
|
|
408
|
+
|
|
405
409
|
except httpx.HTTPStatusError as e:
|
|
406
410
|
if e.response.status_code == 401:
|
|
407
411
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
408
412
|
raise APIError(f"Failed to push stories batch: {e.response.status_code}")
|
|
409
413
|
except httpx.RequestError as e:
|
|
410
414
|
raise APIError(f"Network error: {str(e)}")
|
|
411
|
-
|
|
415
|
+
|
|
412
416
|
return {
|
|
413
417
|
"pushed": total_pushed,
|
|
414
418
|
"failed": total_failed,
|
|
@@ -419,27 +423,324 @@ async def push_stories_batch(stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
419
423
|
async def get_public_profile_settings() -> dict[str, Any]:
|
|
420
424
|
"""
|
|
421
425
|
Get the current user's public profile settings.
|
|
422
|
-
|
|
426
|
+
|
|
423
427
|
Returns:
|
|
424
428
|
Dict with username, is_profile_public, profile_url, etc.
|
|
425
|
-
|
|
429
|
+
|
|
426
430
|
Raises:
|
|
427
431
|
APIError: If request fails
|
|
428
432
|
AuthError: If not authenticated
|
|
429
433
|
"""
|
|
430
|
-
|
|
434
|
+
with httpx.Client() as client:
|
|
431
435
|
try:
|
|
432
|
-
response =
|
|
436
|
+
response = client.get(
|
|
433
437
|
_get_public_settings_url(),
|
|
434
438
|
headers=_get_headers(),
|
|
435
439
|
timeout=30,
|
|
436
440
|
)
|
|
437
441
|
response.raise_for_status()
|
|
438
442
|
return response.json()
|
|
439
|
-
|
|
443
|
+
|
|
440
444
|
except httpx.HTTPStatusError as e:
|
|
441
445
|
if e.response.status_code == 401:
|
|
442
446
|
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
443
447
|
raise APIError(f"Failed to get profile settings: {e.response.status_code}")
|
|
444
448
|
except httpx.RequestError as e:
|
|
445
449
|
raise APIError(f"Network error: {str(e)}")
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
async def set_story_visibility(story_id: str, visibility: str) -> dict[str, Any]:
|
|
453
|
+
"""
|
|
454
|
+
Set story visibility.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
story_id: ID of the story
|
|
458
|
+
visibility: Visibility setting (public, private, friends_only)
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Updated story data
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
APIError: If request fails
|
|
465
|
+
AuthError: If not authenticated
|
|
466
|
+
"""
|
|
467
|
+
with httpx.Client() as client:
|
|
468
|
+
try:
|
|
469
|
+
response = client.patch(
|
|
470
|
+
f"{_get_stories_url()}/{story_id}/visibility",
|
|
471
|
+
headers=_get_headers(),
|
|
472
|
+
json={"visibility": visibility},
|
|
473
|
+
timeout=30,
|
|
474
|
+
)
|
|
475
|
+
response.raise_for_status()
|
|
476
|
+
return response.json()
|
|
477
|
+
|
|
478
|
+
except httpx.HTTPStatusError as e:
|
|
479
|
+
if e.response.status_code == 401:
|
|
480
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
481
|
+
raise APIError(f"Failed to set story visibility: {e.response.status_code}")
|
|
482
|
+
except httpx.RequestError as e:
|
|
483
|
+
raise APIError(f"Network error: {str(e)}")
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
async def get_friends() -> list[dict[str, Any]]:
|
|
487
|
+
"""
|
|
488
|
+
Get friends list.
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
List of friend data dicts
|
|
492
|
+
|
|
493
|
+
Raises:
|
|
494
|
+
APIError: If request fails
|
|
495
|
+
AuthError: If not authenticated
|
|
496
|
+
"""
|
|
497
|
+
with httpx.Client() as client:
|
|
498
|
+
try:
|
|
499
|
+
response = client.get(
|
|
500
|
+
f"{get_api_base()}/friends",
|
|
501
|
+
headers=_get_headers(),
|
|
502
|
+
timeout=30,
|
|
503
|
+
)
|
|
504
|
+
response.raise_for_status()
|
|
505
|
+
return response.json()
|
|
506
|
+
|
|
507
|
+
except httpx.HTTPStatusError as e:
|
|
508
|
+
if e.response.status_code == 401:
|
|
509
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
510
|
+
raise APIError(f"Failed to get friends: {e.response.status_code}")
|
|
511
|
+
except httpx.RequestError as e:
|
|
512
|
+
raise APIError(f"Network error: {str(e)}")
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
async def send_friend_request(username: str) -> dict[str, Any]:
|
|
516
|
+
"""
|
|
517
|
+
Send friend request.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
username: Username to send friend request to
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Friend request data
|
|
524
|
+
|
|
525
|
+
Raises:
|
|
526
|
+
APIError: If request fails
|
|
527
|
+
AuthError: If not authenticated
|
|
528
|
+
"""
|
|
529
|
+
with httpx.Client() as client:
|
|
530
|
+
try:
|
|
531
|
+
response = client.post(
|
|
532
|
+
f"{get_api_base()}/friends/request",
|
|
533
|
+
headers=_get_headers(),
|
|
534
|
+
json={"to_username": username},
|
|
535
|
+
timeout=30,
|
|
536
|
+
)
|
|
537
|
+
response.raise_for_status()
|
|
538
|
+
return response.json()
|
|
539
|
+
|
|
540
|
+
except httpx.HTTPStatusError as e:
|
|
541
|
+
if e.response.status_code == 401:
|
|
542
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
543
|
+
raise APIError(f"Failed to send friend request: {e.response.status_code}")
|
|
544
|
+
except httpx.RequestError as e:
|
|
545
|
+
raise APIError(f"Network error: {str(e)}")
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
async def get_friend_requests() -> list[dict[str, Any]]:
|
|
549
|
+
"""
|
|
550
|
+
Get pending friend requests.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
List of friend request data dicts
|
|
554
|
+
|
|
555
|
+
Raises:
|
|
556
|
+
APIError: If request fails
|
|
557
|
+
AuthError: If not authenticated
|
|
558
|
+
"""
|
|
559
|
+
with httpx.Client() as client:
|
|
560
|
+
try:
|
|
561
|
+
response = client.get(
|
|
562
|
+
f"{get_api_base()}/friends/requests",
|
|
563
|
+
headers=_get_headers(),
|
|
564
|
+
timeout=30,
|
|
565
|
+
)
|
|
566
|
+
response.raise_for_status()
|
|
567
|
+
return response.json()
|
|
568
|
+
|
|
569
|
+
except httpx.HTTPStatusError as e:
|
|
570
|
+
if e.response.status_code == 401:
|
|
571
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
572
|
+
raise APIError(f"Failed to get friend requests: {e.response.status_code}")
|
|
573
|
+
except httpx.RequestError as e:
|
|
574
|
+
raise APIError(f"Network error: {str(e)}")
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
async def approve_friend_request(request_id: str) -> dict[str, Any]:
|
|
578
|
+
"""
|
|
579
|
+
Approve friend request.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
request_id: ID of the friend request to approve
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
Friend request data
|
|
586
|
+
|
|
587
|
+
Raises:
|
|
588
|
+
APIError: If request fails
|
|
589
|
+
AuthError: If not authenticated
|
|
590
|
+
"""
|
|
591
|
+
with httpx.Client() as client:
|
|
592
|
+
try:
|
|
593
|
+
response = client.post(
|
|
594
|
+
f"{get_api_base()}/friends/approve/{request_id}",
|
|
595
|
+
headers=_get_headers(),
|
|
596
|
+
timeout=30,
|
|
597
|
+
)
|
|
598
|
+
response.raise_for_status()
|
|
599
|
+
return response.json()
|
|
600
|
+
|
|
601
|
+
except httpx.HTTPStatusError as e:
|
|
602
|
+
if e.response.status_code == 401:
|
|
603
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
604
|
+
raise APIError(f"Failed to approve friend request: {e.response.status_code}")
|
|
605
|
+
except httpx.RequestError as e:
|
|
606
|
+
raise APIError(f"Network error: {str(e)}")
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
async def reject_friend_request(request_id: str) -> dict[str, Any]:
|
|
610
|
+
"""
|
|
611
|
+
Reject friend request.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
request_id: ID of the friend request to reject
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
Friend request data
|
|
618
|
+
|
|
619
|
+
Raises:
|
|
620
|
+
APIError: If request fails
|
|
621
|
+
AuthError: If not authenticated
|
|
622
|
+
"""
|
|
623
|
+
with httpx.Client() as client:
|
|
624
|
+
try:
|
|
625
|
+
response = client.post(
|
|
626
|
+
f"{get_api_base()}/friends/reject/{request_id}",
|
|
627
|
+
headers=_get_headers(),
|
|
628
|
+
timeout=30,
|
|
629
|
+
)
|
|
630
|
+
response.raise_for_status()
|
|
631
|
+
return response.json()
|
|
632
|
+
|
|
633
|
+
except httpx.HTTPStatusError as e:
|
|
634
|
+
if e.response.status_code == 401:
|
|
635
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
636
|
+
raise APIError(f"Failed to reject friend request: {e.response.status_code}")
|
|
637
|
+
except httpx.RequestError as e:
|
|
638
|
+
raise APIError(f"Network error: {str(e)}")
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
async def get_friend_stories(username: str) -> list[dict[str, Any]]:
|
|
642
|
+
"""
|
|
643
|
+
Get stories from a friend.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
username: Username of the friend
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
List of story data dicts
|
|
650
|
+
|
|
651
|
+
Raises:
|
|
652
|
+
APIError: If request fails
|
|
653
|
+
AuthError: If not authenticated
|
|
654
|
+
"""
|
|
655
|
+
with httpx.Client() as client:
|
|
656
|
+
try:
|
|
657
|
+
response = client.get(
|
|
658
|
+
f"{get_api_base()}/friends/{username}/stories",
|
|
659
|
+
headers=_get_headers(),
|
|
660
|
+
timeout=30,
|
|
661
|
+
)
|
|
662
|
+
response.raise_for_status()
|
|
663
|
+
return response.json()
|
|
664
|
+
|
|
665
|
+
except httpx.HTTPStatusError as e:
|
|
666
|
+
if e.response.status_code == 401:
|
|
667
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
668
|
+
raise APIError(f"Failed to get friend stories: {e.response.status_code}")
|
|
669
|
+
except httpx.RequestError as e:
|
|
670
|
+
raise APIError(f"Network error: {str(e)}")
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
async def get_visibility_settings() -> dict[str, Any]:
|
|
674
|
+
"""
|
|
675
|
+
Get visibility settings from the server.
|
|
676
|
+
|
|
677
|
+
Returns:
|
|
678
|
+
Dict with profile, repos_default, stories_default visibility settings
|
|
679
|
+
|
|
680
|
+
Raises:
|
|
681
|
+
APIError: If request fails
|
|
682
|
+
AuthError: If not authenticated
|
|
683
|
+
"""
|
|
684
|
+
with httpx.Client() as client:
|
|
685
|
+
try:
|
|
686
|
+
response = client.get(
|
|
687
|
+
f"{get_api_base()}/visibility",
|
|
688
|
+
headers=_get_headers(),
|
|
689
|
+
timeout=30,
|
|
690
|
+
)
|
|
691
|
+
response.raise_for_status()
|
|
692
|
+
return response.json()
|
|
693
|
+
|
|
694
|
+
except httpx.HTTPStatusError as e:
|
|
695
|
+
if e.response.status_code == 401:
|
|
696
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
697
|
+
raise APIError(f"Failed to get visibility settings: {e.response.status_code}")
|
|
698
|
+
except httpx.RequestError as e:
|
|
699
|
+
raise APIError(f"Network error: {str(e)}")
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
async def update_visibility_settings(
|
|
703
|
+
profile: str | None = None,
|
|
704
|
+
repos_default: str | None = None,
|
|
705
|
+
stories_default: str | None = None,
|
|
706
|
+
) -> dict[str, Any]:
|
|
707
|
+
"""
|
|
708
|
+
Update visibility settings on the server.
|
|
709
|
+
|
|
710
|
+
Args:
|
|
711
|
+
profile: Profile visibility (public, private, connections)
|
|
712
|
+
repos_default: Default repos visibility (public, private, connections)
|
|
713
|
+
stories_default: Default stories visibility (public, private, connections)
|
|
714
|
+
|
|
715
|
+
Returns:
|
|
716
|
+
Updated visibility settings
|
|
717
|
+
|
|
718
|
+
Raises:
|
|
719
|
+
APIError: If request fails
|
|
720
|
+
AuthError: If not authenticated
|
|
721
|
+
"""
|
|
722
|
+
with httpx.Client() as client:
|
|
723
|
+
try:
|
|
724
|
+
payload = {}
|
|
725
|
+
if profile is not None:
|
|
726
|
+
payload["profile"] = profile
|
|
727
|
+
if repos_default is not None:
|
|
728
|
+
payload["repos_default"] = repos_default
|
|
729
|
+
if stories_default is not None:
|
|
730
|
+
payload["stories_default"] = stories_default
|
|
731
|
+
|
|
732
|
+
response = client.patch(
|
|
733
|
+
f"{get_api_base()}/visibility",
|
|
734
|
+
headers=_get_headers(),
|
|
735
|
+
json=payload,
|
|
736
|
+
timeout=30,
|
|
737
|
+
)
|
|
738
|
+
response.raise_for_status()
|
|
739
|
+
return response.json()
|
|
740
|
+
|
|
741
|
+
except httpx.HTTPStatusError as e:
|
|
742
|
+
if e.response.status_code == 401:
|
|
743
|
+
raise AuthError("Session expired. Please run 'repr login' again.")
|
|
744
|
+
raise APIError(f"Failed to update visibility settings: {e.response.status_code}")
|
|
745
|
+
except httpx.RequestError as e:
|
|
746
|
+
raise APIError(f"Network error: {str(e)}")
|