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.
- tetra_cli/__init__.py +6 -0
- tetra_cli/api_client/__init__.py +10 -0
- tetra_cli/api_client/client.py +173 -0
- tetra_cli/api_client/config.py +125 -0
- tetra_cli/api_client/operations/__init__.py +9 -0
- tetra_cli/api_client/operations/accounts.py +303 -0
- tetra_cli/api_client/operations/ai.py +278 -0
- tetra_cli/api_client/operations/analysis.py +190 -0
- tetra_cli/api_client/operations/api_keys.py +145 -0
- tetra_cli/api_client/operations/archive.py +114 -0
- tetra_cli/api_client/operations/awards.py +123 -0
- tetra_cli/api_client/operations/capacity.py +84 -0
- tetra_cli/api_client/operations/conversations.py +447 -0
- tetra_cli/api_client/operations/conversations_2.py +262 -0
- tetra_cli/api_client/operations/cosmetics.py +148 -0
- tetra_cli/api_client/operations/dashboard.py +282 -0
- tetra_cli/api_client/operations/data.py +250 -0
- tetra_cli/api_client/operations/events.py +734 -0
- tetra_cli/api_client/operations/gamification.py +470 -0
- tetra_cli/api_client/operations/goals.py +1144 -0
- tetra_cli/api_client/operations/groups.py +647 -0
- tetra_cli/api_client/operations/issues.py +198 -0
- tetra_cli/api_client/operations/offset.py +61 -0
- tetra_cli/api_client/operations/onboarding.py +284 -0
- tetra_cli/api_client/operations/outcome_schemas.py +292 -0
- tetra_cli/api_client/operations/peer_connections.py +243 -0
- tetra_cli/api_client/operations/plaid.py +329 -0
- tetra_cli/api_client/operations/reminders.py +273 -0
- tetra_cli/api_client/operations/scratches.py +280 -0
- tetra_cli/api_client/operations/skill_trees.py +160 -0
- tetra_cli/api_client/operations/social_2.py +560 -0
- tetra_cli/api_client/operations/social_3.py +618 -0
- tetra_cli/api_client/operations/social_4.py +527 -0
- tetra_cli/api_client/operations/strava.py +215 -0
- tetra_cli/api_client/operations/stripe.py +113 -0
- tetra_cli/api_client/operations/tags.py +488 -0
- tetra_cli/api_client/operations/values.py +867 -0
- tetra_cli/api_client/operations/values_2.py +584 -0
- tetra_cli/api_client/operations/watch.py +105 -0
- tetra_cli/api_client/operations/webhooks.py +50 -0
- tetra_cli/api_client/operations/xp.py +27 -0
- tetra_cli/cli/__init__.py +5 -0
- tetra_cli/cli/__main__.py +5 -0
- tetra_cli/cli/app.py +86 -0
- tetra_cli/cli/commands/__init__.py +1 -0
- tetra_cli/cli/commands/auth.py +201 -0
- tetra_cli/cli/commands/guide.py +8 -0
- tetra_cli/cli/commands/messages.py +161 -0
- tetra_cli/cli/commands/skill.py +71 -0
- tetra_cli/cli/context.py +13 -0
- tetra_cli/cli/generate.py +282 -0
- tetra_cli/cli/output.py +58 -0
- tetra_cli/mcp_gen.py +137 -0
- tetra_cli/ontology.py +70 -0
- tetra_cli/registry.py +118 -0
- tetra_cli/skill/SKILL.md +69 -0
- tetra_cli/skill/__init__.py +1 -0
- tetra_cli-0.2.0.dist-info/METADATA +140 -0
- tetra_cli-0.2.0.dist-info/RECORD +62 -0
- tetra_cli-0.2.0.dist-info/WHEEL +5 -0
- tetra_cli-0.2.0.dist-info/entry_points.txt +2 -0
- tetra_cli-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
"""Pure async operations for the gamification API.
|
|
2
|
+
|
|
3
|
+
Covers XP progress and history, credits (history + gifting), the item catalog
|
|
4
|
+
and purchases, daily engagement, activities and feature discoveries, streak
|
|
5
|
+
freezes, feature-access checks, contextual XP breakdowns, and the gem (XP-boost)
|
|
6
|
+
catalog and purchases.
|
|
7
|
+
|
|
8
|
+
Each op is a thin async wrapper over ``TetraClient`` that mirrors the backend
|
|
9
|
+
route contract (path, method, params, body field names). Optional body/query
|
|
10
|
+
fields that are ``None`` are omitted so the API applies its own defaults.
|
|
11
|
+
"""
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from tetra_cli.api_client import TetraClient
|
|
15
|
+
from tetra_cli.registry import arg, operation, opt
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# PROGRESS
|
|
19
|
+
# =============================================================================
|
|
20
|
+
|
|
21
|
+
@operation(
|
|
22
|
+
cli="gamification progress",
|
|
23
|
+
summary="Get current gamification progress (level, XP, credits, streak).",
|
|
24
|
+
covers=[("GET", "/api/v1/gamification/progress")],
|
|
25
|
+
)
|
|
26
|
+
async def get_progress(client: TetraClient) -> dict[str, Any]:
|
|
27
|
+
"""Get current gamification progress.
|
|
28
|
+
|
|
29
|
+
Returns level, XP, credits, streak, multiplier, and unlocked features.
|
|
30
|
+
"""
|
|
31
|
+
return await client.get("/api/v1/gamification/progress")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# =============================================================================
|
|
35
|
+
# XP HISTORY & BREAKDOWN
|
|
36
|
+
# =============================================================================
|
|
37
|
+
|
|
38
|
+
@operation(
|
|
39
|
+
cli="gamification xp-history",
|
|
40
|
+
summary="List recent XP ledger entries with their source.",
|
|
41
|
+
covers=[("GET", "/api/v1/gamification/xp/history")],
|
|
42
|
+
params={
|
|
43
|
+
"limit": opt("--limit", min=1, max=200, help="Max entries (1-200)."),
|
|
44
|
+
"offset": opt("--offset", min=0, help="Entries to skip."),
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
async def get_xp_history(
|
|
48
|
+
client: TetraClient,
|
|
49
|
+
*,
|
|
50
|
+
limit: int = 50,
|
|
51
|
+
offset: int = 0,
|
|
52
|
+
) -> dict[str, Any]:
|
|
53
|
+
"""List XP ledger history.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
client: Authenticated TetraClient
|
|
57
|
+
limit: Maximum entries to return (1-200)
|
|
58
|
+
offset: Number of entries to skip (for paging)
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Dict with ``entries`` list and ``total`` count
|
|
62
|
+
"""
|
|
63
|
+
return await client.get(
|
|
64
|
+
"/api/v1/gamification/xp/history",
|
|
65
|
+
params={"limit": limit, "offset": offset},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@operation(
|
|
70
|
+
cli="gamification xp-breakdown",
|
|
71
|
+
summary="Get a contextual XP breakdown for a goal or a value.",
|
|
72
|
+
covers=[("GET", "/api/v1/gamification/xp/breakdown")],
|
|
73
|
+
params={
|
|
74
|
+
"goal_name": opt("--goal-name", help="Goal name to break down."),
|
|
75
|
+
"value_name": opt("--value-name", help="Value name to break down."),
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
async def get_xp_breakdown(
|
|
79
|
+
client: TetraClient,
|
|
80
|
+
*,
|
|
81
|
+
goal_name: str | None = None,
|
|
82
|
+
value_name: str | None = None,
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
"""Get contextual XP breakdown for a goal or value.
|
|
85
|
+
|
|
86
|
+
Exactly one of ``goal_name`` / ``value_name`` should be provided; the
|
|
87
|
+
backend 400s if neither is given. Only the provided filter is sent.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dict with summary stats, source breakdown, weekly stacked data,
|
|
91
|
+
and individual XP entries with formula details.
|
|
92
|
+
"""
|
|
93
|
+
params: dict[str, Any] = {}
|
|
94
|
+
if goal_name is not None:
|
|
95
|
+
params["goal_name"] = goal_name
|
|
96
|
+
if value_name is not None:
|
|
97
|
+
params["value_name"] = value_name
|
|
98
|
+
return await client.get(
|
|
99
|
+
"/api/v1/gamification/xp/breakdown", params=params,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# =============================================================================
|
|
104
|
+
# DAILY ENGAGEMENT
|
|
105
|
+
# =============================================================================
|
|
106
|
+
|
|
107
|
+
@operation(
|
|
108
|
+
cli="gamification engagement-today",
|
|
109
|
+
summary="Get today's engagement record (activities, streak, XP).",
|
|
110
|
+
covers=[("GET", "/api/v1/gamification/engagement/today")],
|
|
111
|
+
)
|
|
112
|
+
async def get_today_engagement(client: TetraClient) -> dict[str, Any]:
|
|
113
|
+
"""Get today's engagement record.
|
|
114
|
+
|
|
115
|
+
Returns activities, streak, discoveries, and XP earned today.
|
|
116
|
+
"""
|
|
117
|
+
return await client.get("/api/v1/gamification/engagement/today")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# =============================================================================
|
|
121
|
+
# CREDITS
|
|
122
|
+
# =============================================================================
|
|
123
|
+
|
|
124
|
+
@operation(
|
|
125
|
+
cli="gamification credit-history",
|
|
126
|
+
summary="List recent credit transactions (earnings and spending).",
|
|
127
|
+
covers=[("GET", "/api/v1/gamification/credits/history")],
|
|
128
|
+
params={
|
|
129
|
+
"limit": opt("--limit", min=1, max=200, help="Max transactions (1-200)."),
|
|
130
|
+
"offset": opt("--offset", min=0, help="Transactions to skip."),
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
async def get_credit_history(
|
|
134
|
+
client: TetraClient,
|
|
135
|
+
*,
|
|
136
|
+
limit: int = 50,
|
|
137
|
+
offset: int = 0,
|
|
138
|
+
) -> dict[str, Any]:
|
|
139
|
+
"""List credit transaction history.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
client: Authenticated TetraClient
|
|
143
|
+
limit: Maximum transactions to return (1-200)
|
|
144
|
+
offset: Number of transactions to skip (for paging)
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Dict with ``transactions`` list and ``total`` count
|
|
148
|
+
"""
|
|
149
|
+
return await client.get(
|
|
150
|
+
"/api/v1/gamification/credits/history",
|
|
151
|
+
params={"limit": limit, "offset": offset},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@operation(
|
|
156
|
+
cli="gamification gift-credits",
|
|
157
|
+
summary="Gift credits to a connected user (daily-capped, user-only).",
|
|
158
|
+
covers=[("POST", "/api/v1/gamification/credits/gift")],
|
|
159
|
+
params={
|
|
160
|
+
"to_account_uid": arg(help="Recipient account UID."),
|
|
161
|
+
"amount": arg(help="Number of credits to gift (positive integer)."),
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
async def gift_credits(
|
|
165
|
+
client: TetraClient,
|
|
166
|
+
*,
|
|
167
|
+
to_account_uid: str,
|
|
168
|
+
amount: int,
|
|
169
|
+
) -> dict[str, Any]:
|
|
170
|
+
"""Gift credits to a connected user.
|
|
171
|
+
|
|
172
|
+
``amount`` is a count of credits (not cents); it is passed through
|
|
173
|
+
unchanged. The backend enforces a positive amount, a connection check,
|
|
174
|
+
and a daily gifting cap.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
client: Authenticated TetraClient
|
|
178
|
+
to_account_uid: Recipient account UID
|
|
179
|
+
amount: Number of credits to transfer
|
|
180
|
+
"""
|
|
181
|
+
return await client.post(
|
|
182
|
+
"/api/v1/gamification/credits/gift",
|
|
183
|
+
json={"to_account_uid": to_account_uid, "amount": amount},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# =============================================================================
|
|
188
|
+
# ITEMS & OWNERSHIP
|
|
189
|
+
# =============================================================================
|
|
190
|
+
|
|
191
|
+
@operation(
|
|
192
|
+
cli="gamification items",
|
|
193
|
+
mcp_tool="list_gamification_items",
|
|
194
|
+
summary="List the item catalog with ownership and purchasable flags.",
|
|
195
|
+
covers=[("GET", "/api/v1/gamification/items")],
|
|
196
|
+
params={
|
|
197
|
+
"category": opt("--category", help="Filter items by category."),
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
async def list_items(
|
|
201
|
+
client: TetraClient,
|
|
202
|
+
*,
|
|
203
|
+
category: str | None = None,
|
|
204
|
+
) -> dict[str, Any]:
|
|
205
|
+
"""List all items with ownership status.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
client: Authenticated TetraClient
|
|
209
|
+
category: Optional category filter
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dict with ``items`` list (each with ``owned``/``purchasable``)
|
|
213
|
+
"""
|
|
214
|
+
params: dict[str, Any] = {}
|
|
215
|
+
if category is not None:
|
|
216
|
+
params["category"] = category
|
|
217
|
+
return await client.get("/api/v1/gamification/items", params=params)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@operation(
|
|
221
|
+
cli="gamification owned",
|
|
222
|
+
summary="List the IDs of items the caller owns.",
|
|
223
|
+
covers=[("GET", "/api/v1/gamification/owned")],
|
|
224
|
+
)
|
|
225
|
+
async def get_owned_items(client: TetraClient) -> dict[str, Any]:
|
|
226
|
+
"""Get the list of owned item IDs.
|
|
227
|
+
|
|
228
|
+
Fast endpoint for checking ownership without full item details.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Dict with ``item_ids`` list
|
|
232
|
+
"""
|
|
233
|
+
return await client.get("/api/v1/gamification/owned")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# =============================================================================
|
|
237
|
+
# PURCHASES
|
|
238
|
+
# =============================================================================
|
|
239
|
+
|
|
240
|
+
@operation(
|
|
241
|
+
cli="gamification purchase",
|
|
242
|
+
summary="Purchase an item with credits.",
|
|
243
|
+
covers=[("POST", "/api/v1/gamification/purchase")],
|
|
244
|
+
params={
|
|
245
|
+
"item_id": arg(help="Item ID to purchase."),
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
async def purchase_item(
|
|
249
|
+
client: TetraClient,
|
|
250
|
+
*,
|
|
251
|
+
item_id: str,
|
|
252
|
+
) -> dict[str, Any]:
|
|
253
|
+
"""Purchase an item with credits.
|
|
254
|
+
|
|
255
|
+
Deducts credits and adds the item to owned items.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
client: Authenticated TetraClient
|
|
259
|
+
item_id: Item ID to purchase
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Dict with ``success``, ``item_id``, ``credit_balance``, ``message``
|
|
263
|
+
"""
|
|
264
|
+
return await client.post(
|
|
265
|
+
"/api/v1/gamification/purchase", json={"item_id": item_id},
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# =============================================================================
|
|
270
|
+
# ACTIVITIES & DISCOVERIES
|
|
271
|
+
# =============================================================================
|
|
272
|
+
|
|
273
|
+
@operation(
|
|
274
|
+
cli="gamification record-activity",
|
|
275
|
+
summary="Record an activity (screen view, app open, etc.).",
|
|
276
|
+
covers=[("POST", "/api/v1/gamification/activity")],
|
|
277
|
+
params={
|
|
278
|
+
"type": arg(help="Activity type (e.g. 'screen_view')."),
|
|
279
|
+
"screen": opt("--screen", help="Screen name for screen_view activities."),
|
|
280
|
+
},
|
|
281
|
+
)
|
|
282
|
+
async def record_activity(
|
|
283
|
+
client: TetraClient,
|
|
284
|
+
*,
|
|
285
|
+
type: str,
|
|
286
|
+
screen: str | None = None,
|
|
287
|
+
) -> dict[str, Any]:
|
|
288
|
+
"""Record an activity.
|
|
289
|
+
|
|
290
|
+
Activities are deduplicated per day and capped at the daily XP limit.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
client: Authenticated TetraClient
|
|
294
|
+
type: Activity type (e.g. 'screen_view', 'app_open')
|
|
295
|
+
screen: Optional screen name (for screen_view activities)
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Dict with ``xp_awarded`` and ``discoveries``
|
|
299
|
+
"""
|
|
300
|
+
body: dict[str, Any] = {"type": type}
|
|
301
|
+
if screen is not None:
|
|
302
|
+
body["screen"] = screen
|
|
303
|
+
return await client.post("/api/v1/gamification/activity", json=body)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@operation(
|
|
307
|
+
cli="gamification record-discovery",
|
|
308
|
+
summary="Record a first-time feature discovery (one-time XP).",
|
|
309
|
+
covers=[("POST", "/api/v1/gamification/discovery")],
|
|
310
|
+
params={
|
|
311
|
+
"feature": arg(help="Feature identifier that was discovered."),
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
async def record_discovery(
|
|
315
|
+
client: TetraClient,
|
|
316
|
+
*,
|
|
317
|
+
feature: str,
|
|
318
|
+
) -> dict[str, Any]:
|
|
319
|
+
"""Record a feature discovery.
|
|
320
|
+
|
|
321
|
+
First-time discoveries award one-time XP (not subject to the daily cap).
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
client: Authenticated TetraClient
|
|
325
|
+
feature: Feature identifier
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dict with ``xp_awarded`` and ``already_discovered``
|
|
329
|
+
"""
|
|
330
|
+
return await client.post(
|
|
331
|
+
"/api/v1/gamification/discovery", json={"feature": feature},
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# =============================================================================
|
|
336
|
+
# STREAK FREEZE
|
|
337
|
+
# =============================================================================
|
|
338
|
+
|
|
339
|
+
@operation(
|
|
340
|
+
cli="gamification streak-freeze",
|
|
341
|
+
summary="Purchase a streak freeze for a missed day.",
|
|
342
|
+
covers=[("POST", "/api/v1/gamification/streak-freeze")],
|
|
343
|
+
params={
|
|
344
|
+
"for_date": arg(help="Date to freeze (YYYY-MM-DD, within grace period)."),
|
|
345
|
+
"freeze_type": opt(
|
|
346
|
+
"--freeze-type",
|
|
347
|
+
choices=["single", "weekend"],
|
|
348
|
+
help="'single' (25 credits) or 'weekend' (40 credits).",
|
|
349
|
+
),
|
|
350
|
+
},
|
|
351
|
+
)
|
|
352
|
+
async def purchase_streak_freeze(
|
|
353
|
+
client: TetraClient,
|
|
354
|
+
*,
|
|
355
|
+
for_date: str,
|
|
356
|
+
freeze_type: str = "single",
|
|
357
|
+
) -> dict[str, Any]:
|
|
358
|
+
"""Purchase a streak freeze for a missed day.
|
|
359
|
+
|
|
360
|
+
Must be purchased within 24 hours of the missed day; limited to 3 freezes
|
|
361
|
+
per 30-day period.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
client: Authenticated TetraClient
|
|
365
|
+
for_date: Date to freeze (ISO YYYY-MM-DD)
|
|
366
|
+
freeze_type: 'single' (25 credits) or 'weekend' (40 credits)
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Dict with ``success``, ``for_date``, ``cost``, ``credit_balance``
|
|
370
|
+
"""
|
|
371
|
+
return await client.post(
|
|
372
|
+
"/api/v1/gamification/streak-freeze",
|
|
373
|
+
json={"for_date": for_date, "freeze_type": freeze_type},
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# =============================================================================
|
|
378
|
+
# FEATURE ACCESS
|
|
379
|
+
# =============================================================================
|
|
380
|
+
|
|
381
|
+
@operation(
|
|
382
|
+
cli="gamification feature-access",
|
|
383
|
+
summary="Check whether the caller can access a gated feature.",
|
|
384
|
+
covers=[("GET", "/api/v1/gamification/features/{feature_id}/accessible")],
|
|
385
|
+
params={
|
|
386
|
+
"feature_id": arg(help="Feature identifier to check access for."),
|
|
387
|
+
},
|
|
388
|
+
)
|
|
389
|
+
async def check_feature_access(
|
|
390
|
+
client: TetraClient,
|
|
391
|
+
feature_id: str,
|
|
392
|
+
) -> dict[str, Any]:
|
|
393
|
+
"""Check if the caller can access a gated feature.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
client: Authenticated TetraClient
|
|
397
|
+
feature_id: Feature identifier
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
Dict with ``feature_id`` and ``accessible``
|
|
401
|
+
"""
|
|
402
|
+
return await client.get(
|
|
403
|
+
f"/api/v1/gamification/features/{feature_id}/accessible",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# =============================================================================
|
|
408
|
+
# GEMS (XP BOOSTS)
|
|
409
|
+
# =============================================================================
|
|
410
|
+
|
|
411
|
+
@operation(
|
|
412
|
+
cli="gamification gems",
|
|
413
|
+
summary="List the gem catalog (credit-priced consumable XP boosts).",
|
|
414
|
+
covers=[("GET", "/api/v1/gamification/gems")],
|
|
415
|
+
)
|
|
416
|
+
async def list_gems(client: TetraClient) -> dict[str, Any]:
|
|
417
|
+
"""List the gem catalog.
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Dict with a ``gems`` list of catalog entries
|
|
421
|
+
"""
|
|
422
|
+
return await client.get("/api/v1/gamification/gems")
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
@operation(
|
|
426
|
+
cli="gamification active-boosts",
|
|
427
|
+
summary="List the caller's currently-active gem XP boosts.",
|
|
428
|
+
covers=[("GET", "/api/v1/gamification/active-xp-boosts")],
|
|
429
|
+
)
|
|
430
|
+
async def get_active_xp_boosts(client: TetraClient) -> dict[str, Any]:
|
|
431
|
+
"""List the caller's currently-active gem boosts.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Dict with a ``boosts`` list (gem_type, goal_uid, multiplier, expiry)
|
|
435
|
+
"""
|
|
436
|
+
return await client.get("/api/v1/gamification/active-xp-boosts")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@operation(
|
|
440
|
+
cli="gamification purchase-gem",
|
|
441
|
+
summary="Buy a gem with credits (goal-scope gems require a goal UID).",
|
|
442
|
+
covers=[("POST", "/api/v1/gamification/gems/{gem_id}/purchase")],
|
|
443
|
+
params={
|
|
444
|
+
"gem_id": arg(help="Gem ID to purchase."),
|
|
445
|
+
"goal_uid": opt("--goal-uid", help="Target goal UID (goal-scope gems only)."),
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
async def purchase_gem(
|
|
449
|
+
client: TetraClient,
|
|
450
|
+
gem_id: str,
|
|
451
|
+
*,
|
|
452
|
+
goal_uid: str | None = None,
|
|
453
|
+
) -> dict[str, Any]:
|
|
454
|
+
"""Buy a gem with credits.
|
|
455
|
+
|
|
456
|
+
Goal-scope gems require ``goal_uid``; account-scope gems leave it null.
|
|
457
|
+
The backend supplies the caller's personal group scope automatically.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
client: Authenticated TetraClient
|
|
461
|
+
gem_id: Gem ID to purchase
|
|
462
|
+
goal_uid: Target goal UID for goal-scope gems
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Purchase result dict
|
|
466
|
+
"""
|
|
467
|
+
body: dict[str, Any] = {"goal_uid": goal_uid}
|
|
468
|
+
return await client.post(
|
|
469
|
+
f"/api/v1/gamification/gems/{gem_id}/purchase", json=body,
|
|
470
|
+
)
|