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