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,262 @@
1
+ """Annotated operations for conversation messaging, reactions, and nudges.
2
+
3
+ This module covers the second slice of the conversations API surface: posting
4
+ reactions, marking conversations read, sending nudges (DM + group), and the
5
+ atomic create-and-send endpoints for DM and group chats, plus entity-conversation
6
+ lookup. The plain (undecorated) agent-polling helpers (``agent/unseen`` and
7
+ ``agent/read``) live in ``operations/conversations.py`` and are intentionally
8
+ not duplicated here.
9
+ """
10
+ from typing import Any
11
+
12
+ from tetra_cli.api_client import TetraClient
13
+ from tetra_cli.registry import arg, operation, opt
14
+
15
+
16
+ @operation(
17
+ cli="conversations react",
18
+ summary="Add or change a reaction emoji on a message.",
19
+ covers=[("POST", "/api/v1/conversations/{conversation_uid}/reactions")],
20
+ params={
21
+ "conversation_uid": arg(help="Conversation UID"),
22
+ "message_uid": arg(help="Interaction (message) UID to react to"),
23
+ "emoji": arg(help="Reaction emoji (1-20 chars)"),
24
+ },
25
+ )
26
+ async def add_reaction(
27
+ client: TetraClient,
28
+ conversation_uid: str,
29
+ *,
30
+ message_uid: str,
31
+ emoji: str,
32
+ ) -> dict[str, Any]:
33
+ """Add or change a reaction on a message.
34
+
35
+ A second reaction by the same account replaces the first — there is at
36
+ most one reaction per account per message.
37
+
38
+ Args:
39
+ client: Authenticated TetraClient
40
+ conversation_uid: Conversation the message belongs to
41
+ message_uid: Interaction UID to react to
42
+ emoji: The reaction emoji
43
+
44
+ Returns:
45
+ The created reaction interaction dict.
46
+ """
47
+ body: dict[str, Any] = {"message_uid": message_uid, "emoji": emoji}
48
+ return await client.post(
49
+ f"/api/v1/conversations/{conversation_uid}/reactions", json=body
50
+ )
51
+
52
+
53
+ @operation(
54
+ cli="conversations unreact",
55
+ summary="Remove a reaction from a message.",
56
+ covers=[
57
+ ("DELETE", "/api/v1/conversations/{conversation_uid}/reactions/{reaction_uid}")
58
+ ],
59
+ params={
60
+ "conversation_uid": arg(help="Conversation UID"),
61
+ "reaction_uid": arg(help="Reaction interaction UID to remove"),
62
+ },
63
+ )
64
+ async def remove_reaction(
65
+ client: TetraClient,
66
+ conversation_uid: str,
67
+ reaction_uid: str,
68
+ ) -> dict[str, Any]:
69
+ """Remove a reaction from a message.
70
+
71
+ Args:
72
+ client: Authenticated TetraClient
73
+ conversation_uid: Conversation the reaction belongs to
74
+ reaction_uid: The reaction interaction UID
75
+
76
+ Returns:
77
+ Removal confirmation dict.
78
+ """
79
+ return await client.delete(
80
+ f"/api/v1/conversations/{conversation_uid}/reactions/{reaction_uid}"
81
+ )
82
+
83
+
84
+ @operation(
85
+ cli="conversations read",
86
+ summary="Mark a conversation as read for the current user.",
87
+ covers=[("POST", "/api/v1/conversations/{conversation_uid}/read")],
88
+ params={"conversation_uid": arg(help="Conversation UID")},
89
+ )
90
+ async def mark_read(
91
+ client: TetraClient, conversation_uid: str
92
+ ) -> dict[str, Any]:
93
+ """Mark a conversation as read (advances the user's read cursor).
94
+
95
+ Args:
96
+ client: Authenticated TetraClient
97
+ conversation_uid: Conversation UID
98
+
99
+ Returns:
100
+ Status confirmation dict.
101
+ """
102
+ return await client.post(
103
+ f"/api/v1/conversations/{conversation_uid}/read"
104
+ )
105
+
106
+
107
+ @operation(
108
+ cli="conversations nudge",
109
+ summary="Send a nudge in a group conversation by conversation UID.",
110
+ covers=[("POST", "/api/v1/conversations/{conversation_uid}/nudge")],
111
+ params={"conversation_uid": arg(help="Group conversation UID")},
112
+ )
113
+ async def send_group_nudge(
114
+ client: TetraClient, conversation_uid: str
115
+ ) -> dict[str, Any]:
116
+ """Send a nudge in a group conversation.
117
+
118
+ Args:
119
+ client: Authenticated TetraClient
120
+ conversation_uid: The group conversation UID
121
+
122
+ Returns:
123
+ Dict with nudge_streak, max_nudge_streak, interaction_uid.
124
+ """
125
+ return await client.post(
126
+ f"/api/v1/conversations/{conversation_uid}/nudge"
127
+ )
128
+
129
+
130
+ @operation(
131
+ cli="conversations dm-nudge",
132
+ summary="Send a nudge in a DM, addressed by the other account's UID.",
133
+ covers=[("POST", "/api/v1/conversations/dm/{account_uid}/nudge")],
134
+ params={"account_uid": arg(help="The other participant's account UID")},
135
+ )
136
+ async def send_dm_nudge(
137
+ client: TetraClient, account_uid: str
138
+ ) -> dict[str, Any]:
139
+ """Send a nudge in a DM conversation.
140
+
141
+ Args:
142
+ client: Authenticated TetraClient
143
+ account_uid: The other participant's account UID
144
+
145
+ Returns:
146
+ Dict with nudge_streak, max_nudge_streak, interaction_uid.
147
+ """
148
+ return await client.post(
149
+ f"/api/v1/conversations/dm/{account_uid}/nudge"
150
+ )
151
+
152
+
153
+ @operation(
154
+ cli="conversations dm-send",
155
+ summary="Create the DM if needed and send the first message atomically.",
156
+ covers=[("POST", "/api/v1/conversations/dm/{account_uid}/messages")],
157
+ params={
158
+ "account_uid": arg(help="The other participant's account UID"),
159
+ "content": arg(help="Message text"),
160
+ },
161
+ )
162
+ async def send_dm_message(
163
+ client: TetraClient,
164
+ account_uid: str,
165
+ *,
166
+ content: str,
167
+ parent_uid: str | None = None,
168
+ ) -> dict[str, Any]:
169
+ """Create-and-send a DM in one round trip.
170
+
171
+ Materializes an ephemeral DM (no DB row exists until first send) and
172
+ delivers the first message. The handler only reads ``content`` and
173
+ ``parent_uid`` from the body.
174
+
175
+ Args:
176
+ client: Authenticated TetraClient
177
+ account_uid: The other participant's account UID
178
+ content: Message text
179
+ parent_uid: Optional parent interaction UID (reserved)
180
+
181
+ Returns:
182
+ Dict with ``conversation`` and ``interaction`` sub-objects.
183
+ """
184
+ body: dict[str, Any] = {"content": content}
185
+ if parent_uid is not None:
186
+ body["parent_uid"] = parent_uid
187
+ return await client.post(
188
+ f"/api/v1/conversations/dm/{account_uid}/messages", json=body
189
+ )
190
+
191
+
192
+ @operation(
193
+ cli="conversations group-send",
194
+ summary="Create the group chat if needed and send the first message atomically.",
195
+ covers=[("POST", "/api/v1/conversations/group/{group_uid}/messages")],
196
+ params={
197
+ "group_uid": arg(help="Group UID"),
198
+ "content": arg(help="Message text"),
199
+ },
200
+ )
201
+ async def send_group_message(
202
+ client: TetraClient,
203
+ group_uid: str,
204
+ *,
205
+ content: str,
206
+ parent_uid: str | None = None,
207
+ ) -> dict[str, Any]:
208
+ """Create-and-send a group message in one round trip.
209
+
210
+ Materializes an ephemeral group conversation (no DB row until first
211
+ send) and delivers the first message. The handler only reads
212
+ ``content`` and ``parent_uid`` from the body.
213
+
214
+ Args:
215
+ client: Authenticated TetraClient
216
+ group_uid: The group UID
217
+ content: Message text
218
+ parent_uid: Optional parent interaction UID (reserved)
219
+
220
+ Returns:
221
+ Dict with ``conversation`` and ``interaction`` sub-objects.
222
+ """
223
+ body: dict[str, Any] = {"content": content}
224
+ if parent_uid is not None:
225
+ body["parent_uid"] = parent_uid
226
+ return await client.post(
227
+ f"/api/v1/conversations/group/{group_uid}/messages", json=body
228
+ )
229
+
230
+
231
+ @operation(
232
+ cli="conversations entity",
233
+ summary="Find or create the conversation for a goal, value, or event.",
234
+ covers=[("GET", "/api/v1/conversations/entity/{entity_type}/{entity_uid}")],
235
+ params={
236
+ "entity_type": arg(help="Entity type: goal | value | event"),
237
+ "entity_uid": arg(help="Entity UID"),
238
+ "group_uid": opt("--group-uid", help="Owning group UID (required)"),
239
+ },
240
+ )
241
+ async def find_or_create_entity_conversation(
242
+ client: TetraClient,
243
+ entity_type: str,
244
+ entity_uid: str,
245
+ *,
246
+ group_uid: str,
247
+ ) -> dict[str, Any]:
248
+ """Find or create a conversation anchored to a goal, value, or event.
249
+
250
+ Args:
251
+ client: Authenticated TetraClient
252
+ entity_type: One of ``goal``, ``value``, ``event``
253
+ entity_uid: The entity UID
254
+ group_uid: Owning group UID (required query parameter)
255
+
256
+ Returns:
257
+ ConversationResponse dict including the anchor interaction UID.
258
+ """
259
+ return await client.get(
260
+ f"/api/v1/conversations/entity/{entity_type}/{entity_uid}",
261
+ params={"group_uid": group_uid},
262
+ )
@@ -0,0 +1,148 @@
1
+ """Pure async operations for the cosmetics API.
2
+
3
+ Cosmetics are credit-purchasable Items (game characters, profile-photo unlock,
4
+ etc.) that an account can own and equip into named slots. These ops are the
5
+ single source of truth for both the CLI and MCP surfaces — each is a thin async
6
+ wrapper over :class:`TetraClient`.
7
+ """
8
+ from typing import Any
9
+
10
+ from tetra_cli.api_client import TetraClient
11
+ from tetra_cli.registry import arg, operation
12
+
13
+
14
+ @operation(
15
+ cli="cosmetics catalog",
16
+ summary="List active cosmetic Items in a category with ownership flags.",
17
+ covers=[("GET", "/api/v1/cosmetics/catalog")],
18
+ params={"category": arg(help="Item category to list (e.g. game_character).")},
19
+ )
20
+ async def get_cosmetics_catalog(
21
+ client: TetraClient, category: str,
22
+ ) -> dict[str, Any]:
23
+ """List all active Items in a category, each flagged with ownership.
24
+
25
+ Args:
26
+ client: Authenticated TetraClient
27
+ category: Item category to filter by (e.g. ``game_character``).
28
+
29
+ Returns:
30
+ A list of catalog item dicts, each with ``id``, ``name``,
31
+ ``description``, ``credit_cost``, and ``owned``.
32
+ """
33
+ return await client.get(
34
+ "/api/v1/cosmetics/catalog", params={"category": category}
35
+ )
36
+
37
+
38
+ @operation(
39
+ cli="cosmetics equipped",
40
+ summary="Show the caller's currently equipped cosmetic slots.",
41
+ covers=[("GET", "/api/v1/cosmetics/equipped")],
42
+ )
43
+ async def get_cosmetics_equipped(client: TetraClient) -> dict[str, Any]:
44
+ """Return all currently equipped cosmetic slots for the caller.
45
+
46
+ Args:
47
+ client: Authenticated TetraClient
48
+
49
+ Returns:
50
+ Dict with a ``slots`` map of slot name to ``{item_id, value,
51
+ equipped_at}``.
52
+ """
53
+ return await client.get("/api/v1/cosmetics/equipped")
54
+
55
+
56
+ @operation(
57
+ cli="cosmetics equip",
58
+ summary="Equip an owned cosmetic Item into a named slot.",
59
+ covers=[("POST", "/api/v1/cosmetics/equip")],
60
+ params={
61
+ "slot": arg(help="Named slot to equip into."),
62
+ "item_id": arg(help="ID of the owned Item to equip."),
63
+ },
64
+ )
65
+ async def equip_cosmetic(
66
+ client: TetraClient, slot: str, item_id: str,
67
+ ) -> dict[str, Any]:
68
+ """Equip an owned Item into a named slot (upsert).
69
+
70
+ Args:
71
+ client: Authenticated TetraClient
72
+ slot: Named slot to equip into.
73
+ item_id: ID of the owned Item to equip.
74
+
75
+ Returns:
76
+ The equipped-slot dict.
77
+
78
+ Raises:
79
+ TetraClientError: 400 ``not_owned`` if the item is not owned.
80
+ """
81
+ body = {"slot": slot, "item_id": item_id}
82
+ return await client.post("/api/v1/cosmetics/equip", json=body)
83
+
84
+
85
+ @operation(
86
+ cli="cosmetics unequip",
87
+ summary="Clear the cosmetic Item from a named slot (no-op if empty).",
88
+ covers=[("POST", "/api/v1/cosmetics/unequip")],
89
+ params={"slot": arg(help="Named slot to clear.")},
90
+ )
91
+ async def unequip_cosmetic(client: TetraClient, slot: str) -> dict[str, Any]:
92
+ """Remove the equipped item from a named slot.
93
+
94
+ Args:
95
+ client: Authenticated TetraClient
96
+ slot: Named slot to clear.
97
+
98
+ Returns:
99
+ ``{"ok": True}``.
100
+ """
101
+ return await client.post("/api/v1/cosmetics/unequip", json={"slot": slot})
102
+
103
+
104
+ @operation(
105
+ cli="cosmetics unlock-profile-photo",
106
+ summary="Purchase the level-gated profile-photo feature unlock.",
107
+ covers=[("POST", "/api/v1/cosmetics/profile-photo/unlock")],
108
+ )
109
+ async def unlock_profile_photo(client: TetraClient) -> dict[str, Any]:
110
+ """Purchase the profile-photo feature unlock (level-gated, costs credits).
111
+
112
+ The endpoint takes no body — eligibility and cost are derived server-side.
113
+
114
+ Args:
115
+ client: Authenticated TetraClient
116
+
117
+ Returns:
118
+ ``{"unlocked": True}`` on success.
119
+
120
+ Raises:
121
+ TetraClientError: 400 ``level_too_low``, item already owned, or
122
+ insufficient credits.
123
+ """
124
+ return await client.post("/api/v1/cosmetics/profile-photo/unlock")
125
+
126
+
127
+ @operation(
128
+ cli="cosmetics set-avatar",
129
+ summary="Set a custom avatar URL (requires the profile-photo unlock).",
130
+ covers=[("PUT", "/api/v1/cosmetics/avatar")],
131
+ params={"url": arg(help="Custom avatar http/https URL (max 2048 chars).")},
132
+ )
133
+ async def set_avatar(client: TetraClient, url: str) -> dict[str, Any]:
134
+ """Set a custom avatar URL for the account.
135
+
136
+ Requires the profile-photo feature unlock; the URL must be http/https.
137
+
138
+ Args:
139
+ client: Authenticated TetraClient
140
+ url: Custom avatar URL.
141
+
142
+ Returns:
143
+ The updated avatar-slot dict.
144
+
145
+ Raises:
146
+ TetraClientError: 400 ``photo_locked`` if the unlock is not purchased.
147
+ """
148
+ return await client.put("/api/v1/cosmetics/avatar", json={"url": url})
@@ -0,0 +1,282 @@
1
+ """Pure async operations for the dashboard API."""
2
+ from typing import Any
3
+
4
+ from tetra_cli.api_client import TetraClient
5
+ from tetra_cli.registry import operation, opt
6
+
7
+
8
+ @operation(
9
+ cli="dashboard week-snapshot",
10
+ summary="Get a week-at-a-glance overview of time allocations vs budget.",
11
+ covers=[("GET", "/api/v1/dashboard/week-snapshot")],
12
+ params={
13
+ "group_uid": opt("--group-uid", help="Group UID for group-scoped view"),
14
+ "target_account_uid": opt(
15
+ "--target-account-uid",
16
+ help="View a specific member's data within a group",
17
+ ),
18
+ },
19
+ )
20
+ async def get_week_snapshot(
21
+ client: TetraClient,
22
+ *,
23
+ week_start: str | None = None,
24
+ group_uid: str | None = None,
25
+ target_account_uid: str | None = None,
26
+ ) -> dict[str, Any]:
27
+ """Get a week-at-a-glance overview showing time allocations vs budget.
28
+
29
+ Args:
30
+ client: Authenticated TetraClient
31
+ week_start: Reference date in YYYY-MM-DD format (defaults to current week)
32
+ group_uid: Group UID for a group-scoped snapshot (omit for personal)
33
+ target_account_uid: View a specific member's data within a group
34
+
35
+ Returns:
36
+ Dict with value_summaries, totals, and event_count
37
+ """
38
+ params: dict[str, Any] = {}
39
+ if week_start:
40
+ # Backend query param is `date` (not week_start); FastAPI silently
41
+ # ignores unknown params, so the name must match exactly.
42
+ params["date"] = week_start
43
+ if group_uid:
44
+ params["group_uid"] = group_uid
45
+ if target_account_uid:
46
+ params["target_account_uid"] = target_account_uid
47
+ return await client.get("/api/v1/dashboard/week-snapshot", params=params)
48
+
49
+
50
+ @operation(
51
+ cli="dashboard feels-by-day",
52
+ summary="Get average feels (mood) by day over a date range.",
53
+ covers=[("GET", "/api/v1/dashboard/feels-by-day")],
54
+ params={
55
+ "group_uid": opt("--group-uid", help="Group UID for group-scoped feels"),
56
+ "target_account_uid": opt(
57
+ "--target-account-uid",
58
+ help="View a specific member's feels within a group",
59
+ ),
60
+ },
61
+ )
62
+ async def get_feels_by_day(
63
+ client: TetraClient,
64
+ *,
65
+ start_date: str | None = None,
66
+ end_date: str | None = None,
67
+ group_uid: str | None = None,
68
+ target_account_uid: str | None = None,
69
+ ) -> dict[str, Any]:
70
+ """Get mood/feels trends over a date range.
71
+
72
+ Args:
73
+ client: Authenticated TetraClient
74
+ start_date: Start date in YYYY-MM-DD format (defaults to 27 days ago)
75
+ end_date: End date in YYYY-MM-DD format (defaults to today)
76
+ group_uid: Group UID for group-scoped feels (omit for personal)
77
+ target_account_uid: View a specific member's feels within a group
78
+
79
+ Returns:
80
+ Dict with 'days' list, each containing date, average feels, and count
81
+ """
82
+ params: dict[str, Any] = {}
83
+ if start_date:
84
+ params["start_date"] = start_date
85
+ if end_date:
86
+ params["end_date"] = end_date
87
+ if group_uid:
88
+ params["group_uid"] = group_uid
89
+ if target_account_uid:
90
+ params["target_account_uid"] = target_account_uid
91
+ return await client.get("/api/v1/dashboard/feels-by-day", params=params)
92
+
93
+
94
+ @operation(
95
+ cli="dashboard day-progress",
96
+ summary="Get today's progress: recorded vs scheduled time.",
97
+ covers=[("GET", "/api/v1/dashboard/day-progress")],
98
+ params={
99
+ "group_uid": opt("--group-uid", help="Group UID for group-scoped progress"),
100
+ "target_account_uid": opt(
101
+ "--target-account-uid",
102
+ help="View a specific member's day progress within a group",
103
+ ),
104
+ },
105
+ )
106
+ async def get_day_progress(
107
+ client: TetraClient,
108
+ *,
109
+ group_uid: str | None = None,
110
+ target_account_uid: str | None = None,
111
+ ) -> dict[str, Any]:
112
+ """Get today's progress showing how much scheduled time has been recorded.
113
+
114
+ Args:
115
+ client: Authenticated TetraClient
116
+ group_uid: Group UID for group-scoped progress (omit for personal)
117
+ target_account_uid: View a specific member's day progress within a group
118
+
119
+ Returns:
120
+ Dict with total_scheduled, total_recorded, and per-value breakdown
121
+ """
122
+ params: dict[str, Any] = {}
123
+ if group_uid:
124
+ params["group_uid"] = group_uid
125
+ if target_account_uid:
126
+ params["target_account_uid"] = target_account_uid
127
+ return await client.get("/api/v1/dashboard/day-progress", params=params)
128
+
129
+
130
+ @operation(
131
+ cli="dashboard week-clock",
132
+ summary="Get the concentric-arc week clock (recorded/scheduled/free hours).",
133
+ covers=[("GET", "/api/v1/dashboard/week-clock")],
134
+ params={
135
+ "group_uid": opt("--group-uid", help="Group UID for group-scoped week clock"),
136
+ "target_account_uid": opt(
137
+ "--target-account-uid",
138
+ help="View a specific member's week clock within a group",
139
+ ),
140
+ },
141
+ )
142
+ async def get_week_clock(
143
+ client: TetraClient,
144
+ *,
145
+ group_uid: str | None = None,
146
+ target_account_uid: str | None = None,
147
+ ) -> dict[str, Any]:
148
+ """Get the compact week-clock ring data for the dashboard.
149
+
150
+ Args:
151
+ client: Authenticated TetraClient
152
+ group_uid: Group UID for group-scoped week clock (omit for personal)
153
+ target_account_uid: View a specific member's week clock within a group
154
+
155
+ Returns:
156
+ Dict with recorded/scheduled-remaining/free hours and elapsed percentages
157
+ """
158
+ params: dict[str, Any] = {}
159
+ if group_uid:
160
+ params["group_uid"] = group_uid
161
+ if target_account_uid:
162
+ params["target_account_uid"] = target_account_uid
163
+ return await client.get("/api/v1/dashboard/week-clock", params=params)
164
+
165
+
166
+ @operation(
167
+ cli="dashboard week-detail",
168
+ summary="Get the composite Week Detail payload (clock, snapshot, comparison).",
169
+ covers=[("GET", "/api/v1/dashboard/week-detail")],
170
+ params={
171
+ "group_uid": opt("--group-uid", help="Group UID for group-scoped detail"),
172
+ "target_account_uid": opt(
173
+ "--target-account-uid",
174
+ help="View a specific member's data within a group",
175
+ ),
176
+ },
177
+ )
178
+ async def get_week_detail(
179
+ client: TetraClient,
180
+ *,
181
+ date: str | None = None,
182
+ group_uid: str | None = None,
183
+ target_account_uid: str | None = None,
184
+ ) -> dict[str, Any]:
185
+ """Get the composite Week Detail payload in one round-trip.
186
+
187
+ Args:
188
+ client: Authenticated TetraClient
189
+ date: Reference date in YYYY-MM-DD format (defaults to current week)
190
+ group_uid: Group UID for group-scoped detail (omit for personal)
191
+ target_account_uid: View a specific member's data within a group
192
+
193
+ Returns:
194
+ Dict with week_clock, week_snapshot, comparison, summary, and event lists
195
+ """
196
+ params: dict[str, Any] = {}
197
+ if date:
198
+ params["date"] = date
199
+ if group_uid:
200
+ params["group_uid"] = group_uid
201
+ if target_account_uid:
202
+ params["target_account_uid"] = target_account_uid
203
+ return await client.get("/api/v1/dashboard/week-detail", params=params)
204
+
205
+
206
+ @operation(
207
+ cli="dashboard active-patterns",
208
+ summary="Get engagement-curated dashboard sections (streaks, on-pace, etc.).",
209
+ covers=[("GET", "/api/v1/dashboard/active-patterns")],
210
+ params={
211
+ "group_uid": opt("--group-uid", help="Group UID for group-scoped patterns"),
212
+ "target_account_uid": opt(
213
+ "--target-account-uid",
214
+ help="View a specific member's patterns within a group",
215
+ ),
216
+ },
217
+ )
218
+ async def get_active_patterns(
219
+ client: TetraClient,
220
+ *,
221
+ group_uid: str | None = None,
222
+ target_account_uid: str | None = None,
223
+ ) -> dict[str, Any]:
224
+ """Get the curated active-patterns sections for the dashboard.
225
+
226
+ Args:
227
+ client: Authenticated TetraClient
228
+ group_uid: Group UID for group-scoped patterns (omit for personal)
229
+ target_account_uid: View a specific member's patterns within a group
230
+
231
+ Returns:
232
+ Dict with streaks, on_pace, recoverable, rhythm_due, recently_active,
233
+ and neglected lists
234
+ """
235
+ params: dict[str, Any] = {}
236
+ if group_uid:
237
+ params["group_uid"] = group_uid
238
+ if target_account_uid:
239
+ params["target_account_uid"] = target_account_uid
240
+ return await client.get("/api/v1/dashboard/active-patterns", params=params)
241
+
242
+
243
+ @operation(
244
+ cli="dashboard values-week-breakdown",
245
+ summary="Get the per-top-level-value breakdown of the week's events and goals.",
246
+ covers=[("GET", "/api/v1/dashboard/values-week-breakdown")],
247
+ params={
248
+ "group_uid": opt("--group-uid", help="Group UID for group-scoped breakdown"),
249
+ "target_account_uid": opt(
250
+ "--target-account-uid",
251
+ help="View a specific member's data within a group",
252
+ ),
253
+ },
254
+ )
255
+ async def get_values_week_breakdown(
256
+ client: TetraClient,
257
+ *,
258
+ date: str | None = None,
259
+ group_uid: str | None = None,
260
+ target_account_uid: str | None = None,
261
+ ) -> dict[str, Any]:
262
+ """Get the per-top-level-value breakdown for the week.
263
+
264
+ Args:
265
+ client: Authenticated TetraClient
266
+ date: Reference date in YYYY-MM-DD format (defaults to current week)
267
+ group_uid: Group UID for group-scoped breakdown (omit for personal)
268
+ target_account_uid: View a specific member's data within a group
269
+
270
+ Returns:
271
+ Dict mapping each top-level value to its week's goals and events
272
+ """
273
+ params: dict[str, Any] = {}
274
+ if date:
275
+ params["date"] = date
276
+ if group_uid:
277
+ params["group_uid"] = group_uid
278
+ if target_account_uid:
279
+ params["target_account_uid"] = target_account_uid
280
+ return await client.get(
281
+ "/api/v1/dashboard/values-week-breakdown", params=params
282
+ )