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,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
+ )