tetra-cli 0.2.0__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 (62) hide show
  1. tetra_cli/__init__.py +6 -0
  2. tetra_cli/api_client/__init__.py +10 -0
  3. tetra_cli/api_client/client.py +173 -0
  4. tetra_cli/api_client/config.py +125 -0
  5. tetra_cli/api_client/operations/__init__.py +9 -0
  6. tetra_cli/api_client/operations/accounts.py +303 -0
  7. tetra_cli/api_client/operations/ai.py +278 -0
  8. tetra_cli/api_client/operations/analysis.py +190 -0
  9. tetra_cli/api_client/operations/api_keys.py +145 -0
  10. tetra_cli/api_client/operations/archive.py +114 -0
  11. tetra_cli/api_client/operations/awards.py +123 -0
  12. tetra_cli/api_client/operations/capacity.py +84 -0
  13. tetra_cli/api_client/operations/conversations.py +447 -0
  14. tetra_cli/api_client/operations/conversations_2.py +262 -0
  15. tetra_cli/api_client/operations/cosmetics.py +148 -0
  16. tetra_cli/api_client/operations/dashboard.py +282 -0
  17. tetra_cli/api_client/operations/data.py +250 -0
  18. tetra_cli/api_client/operations/events.py +734 -0
  19. tetra_cli/api_client/operations/gamification.py +470 -0
  20. tetra_cli/api_client/operations/goals.py +1144 -0
  21. tetra_cli/api_client/operations/groups.py +647 -0
  22. tetra_cli/api_client/operations/issues.py +198 -0
  23. tetra_cli/api_client/operations/offset.py +61 -0
  24. tetra_cli/api_client/operations/onboarding.py +284 -0
  25. tetra_cli/api_client/operations/outcome_schemas.py +292 -0
  26. tetra_cli/api_client/operations/peer_connections.py +243 -0
  27. tetra_cli/api_client/operations/plaid.py +329 -0
  28. tetra_cli/api_client/operations/reminders.py +273 -0
  29. tetra_cli/api_client/operations/scratches.py +280 -0
  30. tetra_cli/api_client/operations/skill_trees.py +160 -0
  31. tetra_cli/api_client/operations/social_2.py +560 -0
  32. tetra_cli/api_client/operations/social_3.py +618 -0
  33. tetra_cli/api_client/operations/social_4.py +527 -0
  34. tetra_cli/api_client/operations/strava.py +215 -0
  35. tetra_cli/api_client/operations/stripe.py +113 -0
  36. tetra_cli/api_client/operations/tags.py +488 -0
  37. tetra_cli/api_client/operations/values.py +867 -0
  38. tetra_cli/api_client/operations/values_2.py +584 -0
  39. tetra_cli/api_client/operations/watch.py +105 -0
  40. tetra_cli/api_client/operations/webhooks.py +50 -0
  41. tetra_cli/api_client/operations/xp.py +27 -0
  42. tetra_cli/cli/__init__.py +5 -0
  43. tetra_cli/cli/__main__.py +5 -0
  44. tetra_cli/cli/app.py +86 -0
  45. tetra_cli/cli/commands/__init__.py +1 -0
  46. tetra_cli/cli/commands/auth.py +201 -0
  47. tetra_cli/cli/commands/guide.py +8 -0
  48. tetra_cli/cli/commands/messages.py +161 -0
  49. tetra_cli/cli/commands/skill.py +71 -0
  50. tetra_cli/cli/context.py +13 -0
  51. tetra_cli/cli/generate.py +282 -0
  52. tetra_cli/cli/output.py +58 -0
  53. tetra_cli/mcp_gen.py +137 -0
  54. tetra_cli/ontology.py +70 -0
  55. tetra_cli/registry.py +118 -0
  56. tetra_cli/skill/SKILL.md +69 -0
  57. tetra_cli/skill/__init__.py +1 -0
  58. tetra_cli-0.2.0.dist-info/METADATA +140 -0
  59. tetra_cli-0.2.0.dist-info/RECORD +62 -0
  60. tetra_cli-0.2.0.dist-info/WHEEL +5 -0
  61. tetra_cli-0.2.0.dist-info/entry_points.txt +2 -0
  62. tetra_cli-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,618 @@
1
+ """Pure async operations for the social API — group membership, ownership,
2
+ content links, and Arena cohort standings/joins.
3
+
4
+ This module wraps a slice of the ``/api/v1/social`` router covering:
5
+
6
+ * group ownership transfer + member removal / leave-for / accept / decline /
7
+ role + label changes,
8
+ * sub-group removal and group content (events listing, goal/value unlink),
9
+ * Arena cohort standings (Everyone + any group) and joins, plus the caller's
10
+ own skill-tree cohort participation.
11
+
12
+ Every op is a thin async wrapper over :class:`TetraClient`; optional body and
13
+ query fields that are ``None`` are omitted so the backend's own defaults apply.
14
+ """
15
+ from typing import Any
16
+ from urllib.parse import urlencode
17
+
18
+ from tetra_cli.api_client import TetraClient
19
+ from tetra_cli.registry import arg, operation, opt
20
+
21
+ # All social routes are mounted under this prefix.
22
+ _BASE = "/api/v1/social"
23
+
24
+
25
+ # =============================================================================
26
+ # OWNERSHIP + MEMBERSHIP
27
+ # =============================================================================
28
+
29
+ @operation(
30
+ cli="social transfer-ownership",
31
+ summary="Transfer group ownership to another member.",
32
+ covers=[("POST", "/api/v1/social/groups/{uid}/transfer")],
33
+ params={
34
+ "uid": arg(help="Group UID whose ownership is being transferred"),
35
+ "new_owner_group_uid": opt(
36
+ "--new-owner",
37
+ help="Group UID of the new owner (a member of this group).",
38
+ ),
39
+ },
40
+ )
41
+ async def transfer_group_ownership(
42
+ client: TetraClient,
43
+ *,
44
+ uid: str,
45
+ new_owner_group_uid: str,
46
+ ) -> dict[str, Any]:
47
+ """Transfer ownership of a group to another member.
48
+
49
+ The new owner is identified by their group UID (people are groups of
50
+ one). ``new_owner_group_uid`` is sent as a query parameter to mirror the
51
+ backend handler signature.
52
+
53
+ Args:
54
+ client: Authenticated TetraClient
55
+ uid: Group UID whose ownership is being transferred
56
+ new_owner_group_uid: Group UID of the new owner
57
+
58
+ Returns:
59
+ The new owner's membership record.
60
+ """
61
+ query = urlencode({"new_owner_group_uid": new_owner_group_uid})
62
+ return await client.post(f"{_BASE}/groups/{uid}/transfer?{query}")
63
+
64
+
65
+ @operation(
66
+ cli="social remove-member",
67
+ summary="Remove (kick) a member from a group.",
68
+ covers=[("DELETE", "/api/v1/social/groups/{uid}/members/{member_uid}")],
69
+ params={
70
+ "uid": arg(help="Group UID"),
71
+ "member_uid": arg(help="Member's group UID to remove"),
72
+ },
73
+ )
74
+ async def remove_group_member(
75
+ client: TetraClient, uid: str, member_uid: str,
76
+ ) -> dict[str, Any]:
77
+ """Remove a member from a group (kick).
78
+
79
+ Args:
80
+ client: Authenticated TetraClient
81
+ uid: Group UID
82
+ member_uid: Group UID of the member to remove
83
+
84
+ Returns:
85
+ Deletion confirmation dict (the endpoint returns 204).
86
+ """
87
+ return await client.delete(f"{_BASE}/groups/{uid}/members/{member_uid}")
88
+
89
+
90
+ @operation(
91
+ cli="social leave-for",
92
+ summary="Remove an owned group from another group.",
93
+ covers=[("POST", "/api/v1/social/groups/{uid}/leave/{member_uid}")],
94
+ params={
95
+ "uid": arg(help="Group UID to leave"),
96
+ "member_uid": arg(help="Owned group UID being withdrawn from the group"),
97
+ },
98
+ )
99
+ async def leave_group_for(
100
+ client: TetraClient, uid: str, member_uid: str,
101
+ ) -> dict[str, Any]:
102
+ """Withdraw an owned group from another group's membership.
103
+
104
+ The caller must own ``member_uid``. The membership is fully deleted.
105
+
106
+ Args:
107
+ client: Authenticated TetraClient
108
+ uid: Group UID to leave
109
+ member_uid: Group UID (owned by the caller) being withdrawn
110
+
111
+ Returns:
112
+ Status dict ``{"status": "left"}``.
113
+ """
114
+ return await client.post(f"{_BASE}/groups/{uid}/leave/{member_uid}", json=None)
115
+
116
+
117
+ @operation(
118
+ cli="social accept-member",
119
+ summary="Accept an invite or approve a join request for a member.",
120
+ covers=[("POST", "/api/v1/social/groups/{uid}/members/{member_uid}/accept")],
121
+ params={
122
+ "uid": arg(help="Group UID"),
123
+ "member_uid": arg(help="Member's group UID (own UID to accept own invite)"),
124
+ "requested_label": opt(
125
+ "--requested-label", choices=["mentor", "mentee", "coop"],
126
+ help="Optionally request a different label on accept.",
127
+ ),
128
+ },
129
+ )
130
+ async def accept_group_member(
131
+ client: TetraClient,
132
+ uid: str,
133
+ member_uid: str,
134
+ *,
135
+ requested_label: str | None = None,
136
+ ) -> dict[str, Any]:
137
+ """Accept an invitation (invitee) or approve a join request (admin).
138
+
139
+ Pass ``requested_label`` to request a different label than the one
140
+ assigned; downgrades auto-accept while upgrades await owner approval.
141
+
142
+ Args:
143
+ client: Authenticated TetraClient
144
+ uid: Group UID
145
+ member_uid: Member's group UID. Use the caller's own personal-group
146
+ UID to accept their own invitation.
147
+ requested_label: Optional requested label (mentor/mentee/coop).
148
+
149
+ Returns:
150
+ The resulting membership record.
151
+ """
152
+ body: dict[str, Any] | None = None
153
+ if requested_label is not None:
154
+ body = {"requested_label": requested_label}
155
+ return await client.post(
156
+ f"{_BASE}/groups/{uid}/members/{member_uid}/accept", json=body,
157
+ )
158
+
159
+
160
+ @operation(
161
+ cli="social decline-member",
162
+ summary="Decline an invite or deny a join request for a member.",
163
+ covers=[("POST", "/api/v1/social/groups/{uid}/members/{member_uid}/decline")],
164
+ params={
165
+ "uid": arg(help="Group UID"),
166
+ "member_uid": arg(help="Member's group UID (own UID to decline own invite)"),
167
+ },
168
+ )
169
+ async def decline_group_member(
170
+ client: TetraClient, uid: str, member_uid: str,
171
+ ) -> dict[str, Any]:
172
+ """Decline an invitation (invitee) or deny a join request (admin).
173
+
174
+ Args:
175
+ client: Authenticated TetraClient
176
+ uid: Group UID
177
+ member_uid: Member's group UID
178
+
179
+ Returns:
180
+ Deletion confirmation dict (the endpoint returns 204).
181
+ """
182
+ return await client.post(
183
+ f"{_BASE}/groups/{uid}/members/{member_uid}/decline", json=None,
184
+ )
185
+
186
+
187
+ @operation(
188
+ cli="social member-label",
189
+ summary="Change a member's role label (mentor/mentee/coop).",
190
+ covers=[("PUT", "/api/v1/social/groups/{uid}/members/{member_uid}/label")],
191
+ params={
192
+ "uid": arg(help="Group UID"),
193
+ "member_uid": arg(help="Member's group UID"),
194
+ "label": opt(
195
+ "--label", choices=["mentor", "mentee", "coop"],
196
+ help="New role label.",
197
+ ),
198
+ },
199
+ )
200
+ async def update_member_label(
201
+ client: TetraClient,
202
+ uid: str,
203
+ member_uid: str,
204
+ *,
205
+ label: str,
206
+ ) -> dict[str, Any]:
207
+ """Change a member's role label (mentor/mentee/coop).
208
+
209
+ Only the group owner may change labels; changing power dynamics triggers
210
+ a consent reset. ``label`` is sent as a query parameter to mirror the
211
+ backend handler signature.
212
+
213
+ Args:
214
+ client: Authenticated TetraClient
215
+ uid: Group UID
216
+ member_uid: Member's group UID
217
+ label: New label — one of mentor/mentee/coop
218
+
219
+ Returns:
220
+ The updated membership record.
221
+ """
222
+ query = urlencode({"label": label})
223
+ return await client.put(
224
+ f"{_BASE}/groups/{uid}/members/{member_uid}/label?{query}",
225
+ )
226
+
227
+
228
+ @operation(
229
+ cli="social member-role",
230
+ summary="Change a member's role (owner/admin/member).",
231
+ covers=[("PUT", "/api/v1/social/groups/{uid}/members/{member_uid}/role")],
232
+ params={
233
+ "uid": arg(help="Group UID"),
234
+ "member_uid": arg(help="Member's group UID"),
235
+ "role": opt(
236
+ "--role", choices=["owner", "admin", "member"],
237
+ help="New role.",
238
+ ),
239
+ },
240
+ )
241
+ async def update_member_role(
242
+ client: TetraClient,
243
+ uid: str,
244
+ member_uid: str,
245
+ *,
246
+ role: str,
247
+ ) -> dict[str, Any]:
248
+ """Change a member's role (owner/admin/member).
249
+
250
+ ``role`` is sent as a query parameter to mirror the backend handler
251
+ signature. Promoting to owner and demoting are handled server-side.
252
+
253
+ Args:
254
+ client: Authenticated TetraClient
255
+ uid: Group UID
256
+ member_uid: Member's group UID
257
+ role: New role — one of owner/admin/member
258
+
259
+ Returns:
260
+ The updated membership record.
261
+ """
262
+ query = urlencode({"role": role})
263
+ return await client.put(
264
+ f"{_BASE}/groups/{uid}/members/{member_uid}/role?{query}",
265
+ )
266
+
267
+
268
+ @operation(
269
+ cli="social remove-sub-group",
270
+ summary="Remove a sub-group from a composite group.",
271
+ covers=[("DELETE", "/api/v1/social/groups/{uid}/sub-groups/{sub_uid}")],
272
+ params={
273
+ "uid": arg(help="Composite group UID"),
274
+ "sub_uid": arg(help="Sub-group UID to remove"),
275
+ },
276
+ )
277
+ async def remove_sub_group(
278
+ client: TetraClient, uid: str, sub_uid: str,
279
+ ) -> dict[str, Any]:
280
+ """Remove a sub-group from a composite group.
281
+
282
+ Args:
283
+ client: Authenticated TetraClient
284
+ uid: Composite group UID
285
+ sub_uid: Sub-group UID to remove
286
+
287
+ Returns:
288
+ Deletion confirmation dict (the endpoint returns 204).
289
+ """
290
+ return await client.delete(f"{_BASE}/groups/{uid}/sub-groups/{sub_uid}")
291
+
292
+
293
+ # =============================================================================
294
+ # GROUP CONTENT
295
+ # =============================================================================
296
+
297
+ @operation(
298
+ cli="social group-events",
299
+ summary="List events from group members' mapped goals and values.",
300
+ covers=[("GET", "/api/v1/social/groups/{uid}/content/events")],
301
+ params={
302
+ "uid": arg(help="Group UID"),
303
+ "member_uid": opt("--member-uid", help="Filter by member account UID."),
304
+ "goal": opt("--goal", help="Filter by group goal name."),
305
+ "value": opt("--value", help="Filter by group value name."),
306
+ "start_date": opt("--start-date", help="On or after (YYYY-MM-DD)."),
307
+ "end_date": opt("--end-date", help="On or before (YYYY-MM-DD)."),
308
+ "limit": opt("--limit", min=1, max=200, help="Pagination limit (1-200)."),
309
+ "offset": opt("--offset", min=0, help="Pagination offset."),
310
+ },
311
+ )
312
+ async def list_group_member_events(
313
+ client: TetraClient,
314
+ uid: str,
315
+ *,
316
+ member_uid: str | None = None,
317
+ goal: str | None = None,
318
+ value: str | None = None,
319
+ start_date: str | None = None,
320
+ end_date: str | None = None,
321
+ limit: int | None = None,
322
+ offset: int | None = None,
323
+ ) -> dict[str, Any]:
324
+ """List events from group members' mapped goals and values.
325
+
326
+ Returns events with member attribution and group goal/value context.
327
+ Any active member can view; blind groups return an empty result. All
328
+ filters are optional query parameters; ``None`` values are dropped by the
329
+ client so the backend defaults (limit=50, offset=0) apply.
330
+
331
+ Args:
332
+ client: Authenticated TetraClient
333
+ uid: Group UID
334
+ member_uid: Filter by member account UID
335
+ goal: Filter by group goal name
336
+ value: Filter by group value name
337
+ start_date: Inclusive lower bound (YYYY-MM-DD)
338
+ end_date: Inclusive upper bound (YYYY-MM-DD)
339
+ limit: Pagination limit (1-200)
340
+ offset: Pagination offset (>=0)
341
+
342
+ Returns:
343
+ Dict with ``events``, ``total``, and ``members``.
344
+ """
345
+ params: dict[str, Any] = {
346
+ "member_uid": member_uid,
347
+ "goal": goal,
348
+ "value": value,
349
+ "start_date": start_date,
350
+ "end_date": end_date,
351
+ "limit": limit,
352
+ "offset": offset,
353
+ }
354
+ return await client.get(f"{_BASE}/groups/{uid}/content/events", params=params)
355
+
356
+
357
+ @operation(
358
+ cli="social unlink-goal",
359
+ summary="Unlink a personal goal from a group goal.",
360
+ covers=[("DELETE", "/api/v1/social/groups/{uid}/content/goals/{goal_uid}/link")],
361
+ params={
362
+ "uid": arg(help="Group UID"),
363
+ "goal_uid": arg(help="Group goal UID to unlink"),
364
+ },
365
+ )
366
+ async def unlink_group_goal(
367
+ client: TetraClient, uid: str, goal_uid: str,
368
+ ) -> dict[str, Any]:
369
+ """Remove the link between a personal goal and a group goal.
370
+
371
+ Args:
372
+ client: Authenticated TetraClient
373
+ uid: Group UID
374
+ goal_uid: Group goal UID to unlink
375
+
376
+ Returns:
377
+ Deletion confirmation dict (the endpoint returns 204).
378
+ """
379
+ return await client.delete(
380
+ f"{_BASE}/groups/{uid}/content/goals/{goal_uid}/link",
381
+ )
382
+
383
+
384
+ @operation(
385
+ cli="social unlink-value",
386
+ summary="Unlink a personal value from a group value.",
387
+ covers=[("DELETE", "/api/v1/social/groups/{uid}/content/values/{val_uid}/link")],
388
+ params={
389
+ "uid": arg(help="Group UID"),
390
+ "val_uid": arg(help="Group value UID to unlink"),
391
+ },
392
+ )
393
+ async def unlink_group_value(
394
+ client: TetraClient, uid: str, val_uid: str,
395
+ ) -> dict[str, Any]:
396
+ """Remove the link between a personal value and a group value.
397
+
398
+ Args:
399
+ client: Authenticated TetraClient
400
+ uid: Group UID
401
+ val_uid: Group value UID to unlink
402
+
403
+ Returns:
404
+ Deletion confirmation dict (the endpoint returns 204).
405
+ """
406
+ return await client.delete(
407
+ f"{_BASE}/groups/{uid}/content/values/{val_uid}/link",
408
+ )
409
+
410
+
411
+ # =============================================================================
412
+ # ARENA — EVERYONE COHORTS
413
+ # =============================================================================
414
+
415
+ @operation(
416
+ cli="social everyone-tree-standings",
417
+ summary="Leaderboard + viewer standing for an Everyone skill-tree cohort.",
418
+ covers=[(
419
+ "GET",
420
+ "/api/v1/social/groups/everyone/skill-trees/{cohort_value_uid}/standings",
421
+ )],
422
+ params={"cohort_value_uid": arg(help="Cohort value (skill tree) UID")},
423
+ )
424
+ async def everyone_tree_standings(
425
+ client: TetraClient, cohort_value_uid: str,
426
+ ) -> dict[str, Any]:
427
+ """Top-50 leaderboard plus the viewer's standing for one Everyone skill tree.
428
+
429
+ The viewer's row is always included even when outside the top 50; an empty
430
+ cohort returns an empty leaderboard and a viewer with ``rank=None``.
431
+
432
+ Args:
433
+ client: Authenticated TetraClient
434
+ cohort_value_uid: Cohort value (skill tree) UID
435
+
436
+ Returns:
437
+ Standings dict (leaderboard + viewer).
438
+ """
439
+ return await client.get(
440
+ f"{_BASE}/groups/everyone/skill-trees/{cohort_value_uid}/standings",
441
+ )
442
+
443
+
444
+ @operation(
445
+ cli="social everyone-goal-standings",
446
+ summary="Leaderboard + viewer standing for an Everyone goal cohort.",
447
+ covers=[(
448
+ "GET",
449
+ "/api/v1/social/groups/everyone/goals/{cohort_goal_uid}/standings",
450
+ )],
451
+ params={"cohort_goal_uid": arg(help="Cohort goal UID")},
452
+ )
453
+ async def everyone_goal_standings(
454
+ client: TetraClient, cohort_goal_uid: str,
455
+ ) -> dict[str, Any]:
456
+ """Top-50 leaderboard plus the viewer's standing for one Everyone goal cohort.
457
+
458
+ Members are ranked by their best single-event score descending.
459
+
460
+ Args:
461
+ client: Authenticated TetraClient
462
+ cohort_goal_uid: Cohort goal UID
463
+
464
+ Returns:
465
+ Standings dict (leaderboard + viewer).
466
+ """
467
+ return await client.get(
468
+ f"{_BASE}/groups/everyone/goals/{cohort_goal_uid}/standings",
469
+ )
470
+
471
+
472
+ @operation(
473
+ cli="social everyone-join-tree",
474
+ summary="Adopt a published skill tree and join its Everyone cohort.",
475
+ covers=[(
476
+ "POST",
477
+ "/api/v1/social/groups/everyone/skill-trees/{cohort_value_uid}/join",
478
+ )],
479
+ params={"cohort_value_uid": arg(help="Cohort value (skill tree) UID")},
480
+ )
481
+ async def everyone_join_tree(
482
+ client: TetraClient, cohort_value_uid: str,
483
+ ) -> dict[str, Any]:
484
+ """Adopt a published skill tree and join its Everyone cohort.
485
+
486
+ Idempotent: when already a member, returns the current standing without
487
+ re-adopting.
488
+
489
+ Args:
490
+ client: Authenticated TetraClient
491
+ cohort_value_uid: Cohort value (skill tree) UID
492
+
493
+ Returns:
494
+ The viewer's standing in the joined cohort.
495
+ """
496
+ return await client.post(
497
+ f"{_BASE}/groups/everyone/skill-trees/{cohort_value_uid}/join", json=None,
498
+ )
499
+
500
+
501
+ @operation(
502
+ cli="social everyone-join-goal",
503
+ summary="Adopt a public goal and join its Everyone cohort.",
504
+ covers=[("POST", "/api/v1/social/groups/everyone/goals/{cohort_goal_uid}/join")],
505
+ params={"cohort_goal_uid": arg(help="Cohort goal UID")},
506
+ )
507
+ async def everyone_join_goal(
508
+ client: TetraClient, cohort_goal_uid: str,
509
+ ) -> dict[str, Any]:
510
+ """Adopt a public goal and join its Everyone cohort.
511
+
512
+ Idempotent via the backend's "Already linked" guard.
513
+
514
+ Args:
515
+ client: Authenticated TetraClient
516
+ cohort_goal_uid: Cohort goal UID
517
+
518
+ Returns:
519
+ The viewer's standing in the joined cohort.
520
+ """
521
+ return await client.post(
522
+ f"{_BASE}/groups/everyone/goals/{cohort_goal_uid}/join", json=None,
523
+ )
524
+
525
+
526
+ @operation(
527
+ cli="social my-tree-cohorts",
528
+ summary="List the cohorts the caller's personal skill tree participates in.",
529
+ covers=[("GET", "/api/v1/social/skill-trees/mine/{source_value_uid}/cohorts")],
530
+ params={"source_value_uid": arg(help="Root value UID of the caller's tree")},
531
+ )
532
+ async def my_tree_cohorts(
533
+ client: TetraClient, source_value_uid: str,
534
+ ) -> dict[str, Any]:
535
+ """List every cohort the caller's personal tree participates in.
536
+
537
+ Walks the caller's own ValueLinks from ``source_value_uid`` and reports
538
+ their standing (rank, size, percentile, tier, is_god) in each linked
539
+ cohort. Account-scoped to the caller.
540
+
541
+ Args:
542
+ client: Authenticated TetraClient
543
+ source_value_uid: Root value UID of the caller's skill tree
544
+
545
+ Returns:
546
+ Dict with a ``cohorts`` list.
547
+ """
548
+ return await client.get(
549
+ f"{_BASE}/skill-trees/mine/{source_value_uid}/cohorts",
550
+ )
551
+
552
+
553
+ # =============================================================================
554
+ # ARENA — GROUP-SCOPED COHORTS
555
+ # =============================================================================
556
+
557
+ @operation(
558
+ cli="social group-tree-standings",
559
+ summary="Leaderboard + viewer standing for a skill-tree cohort in a group.",
560
+ covers=[(
561
+ "GET",
562
+ "/api/v1/social/groups/{group_uid}/arena/skill-trees/{cohort_value_uid}/standings",
563
+ )],
564
+ params={
565
+ "group_uid": arg(help="Group UID"),
566
+ "cohort_value_uid": arg(help="Cohort value (skill tree) UID"),
567
+ },
568
+ )
569
+ async def group_tree_standings(
570
+ client: TetraClient, group_uid: str, cohort_value_uid: str,
571
+ ) -> dict[str, Any]:
572
+ """Top-50 leaderboard plus the viewer's standing for one skill tree in a group.
573
+
574
+ Requires the viewer to be a member of the group.
575
+
576
+ Args:
577
+ client: Authenticated TetraClient
578
+ group_uid: Group UID
579
+ cohort_value_uid: Cohort value (skill tree) UID
580
+
581
+ Returns:
582
+ Standings dict (leaderboard + viewer).
583
+ """
584
+ return await client.get(
585
+ f"{_BASE}/groups/{group_uid}/arena/skill-trees/{cohort_value_uid}/standings",
586
+ )
587
+
588
+
589
+ @operation(
590
+ cli="social group-goal-standings",
591
+ summary="Leaderboard + viewer standing for a goal cohort in a group.",
592
+ covers=[(
593
+ "GET",
594
+ "/api/v1/social/groups/{group_uid}/arena/goals/{cohort_goal_uid}/standings",
595
+ )],
596
+ params={
597
+ "group_uid": arg(help="Group UID"),
598
+ "cohort_goal_uid": arg(help="Cohort goal UID"),
599
+ },
600
+ )
601
+ async def group_goal_standings(
602
+ client: TetraClient, group_uid: str, cohort_goal_uid: str,
603
+ ) -> dict[str, Any]:
604
+ """Top-50 leaderboard plus the viewer's standing for one goal cohort in a group.
605
+
606
+ Requires the viewer to be a member of the group.
607
+
608
+ Args:
609
+ client: Authenticated TetraClient
610
+ group_uid: Group UID
611
+ cohort_goal_uid: Cohort goal UID
612
+
613
+ Returns:
614
+ Standings dict (leaderboard + viewer).
615
+ """
616
+ return await client.get(
617
+ f"{_BASE}/groups/{group_uid}/arena/goals/{cohort_goal_uid}/standings",
618
+ )