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.
Files changed (49) 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 +4306 -364
  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-B-aCjaCw.js +384 -0
  12. repr/dashboard/dist/assets/index-BYFVbEev.css +1 -0
  13. repr/dashboard/dist/assets/index-BrrhyJFO.css +1 -0
  14. repr/dashboard/dist/assets/index-C7Gzxc4f.js +384 -0
  15. repr/dashboard/dist/assets/index-CQdMXo6g.js +391 -0
  16. repr/dashboard/dist/assets/index-CcEg74ts.js +270 -0
  17. repr/dashboard/dist/assets/index-Cerc-iA_.js +377 -0
  18. repr/dashboard/dist/assets/index-CjVcBW2L.css +1 -0
  19. repr/dashboard/dist/assets/index-Cs8ofFGd.js +384 -0
  20. repr/dashboard/dist/assets/index-Dfl3mR5E.js +377 -0
  21. repr/dashboard/dist/assets/index-DwN0SeMc.css +1 -0
  22. repr/dashboard/dist/assets/index-YFch_e0S.js +384 -0
  23. repr/dashboard/dist/favicon.svg +4 -0
  24. repr/dashboard/dist/index.html +14 -0
  25. repr/dashboard/manager.py +234 -0
  26. repr/dashboard/server.py +1489 -0
  27. repr/db.py +980 -0
  28. repr/hooks.py +3 -2
  29. repr/loaders/__init__.py +22 -0
  30. repr/loaders/base.py +156 -0
  31. repr/loaders/claude_code.py +287 -0
  32. repr/loaders/clawdbot.py +313 -0
  33. repr/loaders/gemini_antigravity.py +381 -0
  34. repr/mcp_server.py +1196 -0
  35. repr/models.py +503 -0
  36. repr/openai_analysis.py +25 -0
  37. repr/session_extractor.py +481 -0
  38. repr/storage.py +328 -0
  39. repr/story_synthesis.py +1296 -0
  40. repr/templates.py +68 -4
  41. repr/timeline.py +710 -0
  42. repr/tools.py +17 -8
  43. {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/METADATA +48 -10
  44. repr_cli-0.2.18.dist-info/RECORD +58 -0
  45. {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/WHEEL +1 -1
  46. {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/entry_points.txt +1 -0
  47. repr_cli-0.2.16.dist-info/RECORD +0 -26
  48. {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/licenses/LICENSE +0 -0
  49. {repr_cli-0.2.16.dist-info → repr_cli-0.2.18.dist-info}/top_level.txt +0 -0
repr/__init__.py CHANGED
@@ -10,6 +10,6 @@ try:
10
10
  __version__ = version("repr-cli")
11
11
  except Exception:
12
12
  # Fallback for PyInstaller builds where metadata isn't available
13
- __version__ = "0.2.8"
13
+ __version__ = "0.2.18"
14
14
  __author__ = "Repr"
15
15
  __email__ = "hello@repr.dev"
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
- async with httpx.AsyncClient() as client:
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 = await client.post(
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
- async with httpx.AsyncClient() as client:
120
+ with httpx.Client() as client:
117
121
  try:
118
- response = await client.get(
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
- async with httpx.AsyncClient() as client:
153
+ with httpx.Client() as client:
150
154
  try:
151
- response = await client.get(
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
- async with httpx.AsyncClient() as client:
182
+ with httpx.Client() as client:
179
183
  try:
180
- response = await client.delete(
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
- async with httpx.AsyncClient() as client:
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 = await client.post(
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
- async with httpx.AsyncClient() as client:
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 = await client.get(
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
- async with httpx.AsyncClient() as client:
348
+ with httpx.Client() as client:
345
349
  try:
346
- response = await client.post(
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
- async with httpx.AsyncClient() as client:
393
+
394
+ with httpx.Client() as client:
391
395
  try:
392
- response = await client.post(
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
- async with httpx.AsyncClient() as client:
434
+ with httpx.Client() as client:
431
435
  try:
432
- response = await client.get(
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)}")