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,447 @@
1
+ """Pure async operations for the conversations / AI-thread messages API."""
2
+ from typing import Any
3
+
4
+ from tetra_cli.api_client import TetraClient
5
+ from tetra_cli.registry import arg, operation, opt
6
+
7
+
8
+ @operation(
9
+ cli="conversations list",
10
+ summary="List the caller's conversations with previews and unread counts.",
11
+ covers=[("GET", "/api/v1/conversations")],
12
+ params={
13
+ "include_muted": opt("--include-muted",
14
+ help="Include muted conversations in the list."),
15
+ },
16
+ )
17
+ async def list_conversations(
18
+ client: TetraClient, *, include_muted: bool = False,
19
+ ) -> dict[str, Any]:
20
+ """List all conversations for the caller.
21
+
22
+ Args:
23
+ client: Authenticated TetraClient.
24
+ include_muted: When True, muted conversations are included; by
25
+ default they are hidden (matching the web inbox).
26
+
27
+ Returns:
28
+ Dict with a ``conversations`` list, each carrying last-message
29
+ preview, unread count, participants, and mute state.
30
+ """
31
+ params: dict[str, Any] = {"include_muted": include_muted}
32
+ return await client.get("/api/v1/conversations", params=params)
33
+
34
+
35
+ @operation(
36
+ cli="conversations ai",
37
+ summary="Get (creating if needed) the caller's AI conversation.",
38
+ covers=[("GET", "/api/v1/conversations/ai")],
39
+ )
40
+ async def get_ai_conversation(client: TetraClient) -> dict[str, Any]:
41
+ """Return (creating if needed) the caller's AI conversation."""
42
+ return await client.get("/api/v1/conversations/ai")
43
+
44
+
45
+ @operation(
46
+ cli="conversations stream",
47
+ summary="Open the agent SSE stream of the caller's AI-thread interactions.",
48
+ covers=[("GET", "/api/v1/conversations/ai/stream")],
49
+ output="stream",
50
+ manual=True,
51
+ )
52
+ async def stream_ai_thread(client: TetraClient) -> Any:
53
+ """Open the agent-only SSE stream for the caller's AI thread.
54
+
55
+ This endpoint returns a ``text/event-stream`` (Server-Sent Events), not a
56
+ JSON body, and requires API-key auth. It is exposed here so the agent
57
+ surface can advertise the stream; consumers that need the live frames must
58
+ read the raw response rather than the parsed JSON the standard client
59
+ returns. Marked ``manual`` so the generators treat it as a streaming op.
60
+
61
+ Args:
62
+ client: Authenticated TetraClient (must carry an API key).
63
+
64
+ Returns:
65
+ Whatever the underlying client yields for the stream endpoint.
66
+ """
67
+ return await client.get("/api/v1/conversations/ai/stream")
68
+
69
+
70
+ @operation(
71
+ cli="conversations attachable-scope",
72
+ summary="List group UIDs whose entities may be attached in a conversation.",
73
+ covers=[("GET", "/api/v1/conversations/{conversation_uid}/attachable-scope")],
74
+ params={"conversation_uid": arg(help="Conversation UID")},
75
+ )
76
+ async def get_attachable_scope(
77
+ client: TetraClient, conversation_uid: str,
78
+ ) -> dict[str, Any]:
79
+ """Return the group UIDs whose entities may be attached here.
80
+
81
+ Args:
82
+ client: Authenticated TetraClient.
83
+ conversation_uid: Conversation UID to scope.
84
+
85
+ Returns:
86
+ Dict with a sorted ``group_uids`` list.
87
+ """
88
+ return await client.get(
89
+ f"/api/v1/conversations/{conversation_uid}/attachable-scope"
90
+ )
91
+
92
+
93
+ @operation(
94
+ cli="conversations messages",
95
+ summary="Get a page of a conversation's messages (newest first).",
96
+ covers=[("GET", "/api/v1/conversations/{conversation_uid}/messages")],
97
+ params={
98
+ "conversation_uid": arg(help="Conversation UID"),
99
+ "limit": opt("--limit", min=1, max=100,
100
+ help="Max messages to return (1-100, default 50)."),
101
+ "cursor": opt("--cursor", help="ISO datetime cursor for pagination."),
102
+ },
103
+ )
104
+ async def get_messages(
105
+ client: TetraClient,
106
+ conversation_uid: str,
107
+ *,
108
+ cursor: str | None = None,
109
+ limit: int = 50,
110
+ ) -> dict[str, Any]:
111
+ """Return a page of messages (newest first)."""
112
+ params: dict[str, Any] = {"limit": limit}
113
+ if cursor:
114
+ params["cursor"] = cursor
115
+ return await client.get(
116
+ f"/api/v1/conversations/{conversation_uid}/messages", params=params
117
+ )
118
+
119
+
120
+ @operation(
121
+ cli="conversations unseen",
122
+ summary="List interactions this identity has not seen (oldest first).",
123
+ covers=[("GET", "/api/v1/conversations/{conversation_uid}/agent/unseen")],
124
+ params={
125
+ "conversation_uid": arg(help="Conversation UID"),
126
+ "limit": opt("--limit", min=1, max=500, help="Max interactions (default 200)."),
127
+ },
128
+ )
129
+ async def get_unseen(
130
+ client: TetraClient, conversation_uid: str, *, limit: int = 200
131
+ ) -> dict[str, Any]:
132
+ """Return interactions this API-key identity has not seen (oldest first)."""
133
+ return await client.get(
134
+ f"/api/v1/conversations/{conversation_uid}/agent/unseen",
135
+ params={"limit": limit},
136
+ )
137
+
138
+
139
+ @operation(
140
+ cli="conversations mark-read",
141
+ summary="Advance this identity's read cursor to now.",
142
+ covers=[("POST", "/api/v1/conversations/{conversation_uid}/agent/read")],
143
+ params={"conversation_uid": arg(help="Conversation UID")},
144
+ )
145
+ async def mark_agent_read(client: TetraClient, conversation_uid: str) -> dict[str, Any]:
146
+ """Advance this API-key identity's read cursor to now."""
147
+ return await client.post(
148
+ f"/api/v1/conversations/{conversation_uid}/agent/read"
149
+ )
150
+
151
+
152
+ @operation(
153
+ cli="conversations send",
154
+ summary="Post a plain text message to a conversation.",
155
+ covers=[("POST", "/api/v1/conversations/{conversation_uid}/messages")],
156
+ params={
157
+ "conversation_uid": arg(help="Conversation UID"),
158
+ "content": opt("--content", help="Message text."),
159
+ "reply_to_uid": opt("--reply-to", help="Quoted-reply target interaction UID."),
160
+ },
161
+ )
162
+ async def post_message(
163
+ client: TetraClient,
164
+ conversation_uid: str,
165
+ *,
166
+ content: str,
167
+ reply_to_uid: str | None = None,
168
+ ) -> dict[str, Any]:
169
+ """Post a plain text message (optionally a quoted reply)."""
170
+ body: dict[str, Any] = {"content": content}
171
+ if reply_to_uid:
172
+ body["reply_to_uid"] = reply_to_uid
173
+ return await client.post(
174
+ f"/api/v1/conversations/{conversation_uid}/messages", json=body
175
+ )
176
+
177
+
178
+ @operation(
179
+ cli="conversations action",
180
+ summary="Post a structured action report to a conversation.",
181
+ covers=[("POST", "/api/v1/conversations/{conversation_uid}/messages")],
182
+ params={
183
+ "conversation_uid": arg(help="Conversation UID"),
184
+ "content": opt("--content", help="One-line human-readable summary."),
185
+ "action": opt("--action", json=True,
186
+ help="ActionPayload as a JSON object (action_type, subject_type, ...)."),
187
+ "reason": opt("--reason", help="Why the action was taken (merged into action.reason)."),
188
+ "reply_to_uid": opt("--reply-to", help="Quoted-reply target interaction UID."),
189
+ },
190
+ )
191
+ async def post_action(
192
+ client: TetraClient,
193
+ conversation_uid: str,
194
+ *,
195
+ content: str,
196
+ action: dict[str, Any],
197
+ reason: str | None = None,
198
+ reply_to_uid: str | None = None,
199
+ ) -> dict[str, Any]:
200
+ """Post a structured action report.
201
+
202
+ Args:
203
+ content: One-line human-readable summary.
204
+ action: ActionPayload dict (action_type, subject_type, subject_uid, ...).
205
+ reason: Convenience — merged into ``action['reason']`` if given.
206
+ reply_to_uid: Optional quoted-reply target.
207
+ """
208
+ payload = dict(action)
209
+ if reason is not None:
210
+ payload["reason"] = reason
211
+ body: dict[str, Any] = {
212
+ "content": content,
213
+ "interaction_type": "action",
214
+ "action": payload,
215
+ }
216
+ if reply_to_uid:
217
+ body["reply_to_uid"] = reply_to_uid
218
+ return await client.post(
219
+ f"/api/v1/conversations/{conversation_uid}/messages", json=body
220
+ )
221
+
222
+
223
+ @operation(
224
+ cli="conversations mute",
225
+ summary="Mute a conversation for the caller.",
226
+ covers=[("POST", "/api/v1/conversations/{conversation_uid}/mute")],
227
+ params={"conversation_uid": arg(help="Conversation UID to mute")},
228
+ )
229
+ async def mute_conversation(
230
+ client: TetraClient, conversation_uid: str,
231
+ ) -> dict[str, Any]:
232
+ """Mute a conversation for the caller.
233
+
234
+ Args:
235
+ client: Authenticated TetraClient.
236
+ conversation_uid: Conversation UID to mute.
237
+
238
+ Returns:
239
+ Dict with ``is_muted: True``.
240
+ """
241
+ return await client.post(f"/api/v1/conversations/{conversation_uid}/mute")
242
+
243
+
244
+ @operation(
245
+ cli="conversations unmute",
246
+ summary="Unmute a conversation for the caller.",
247
+ covers=[("DELETE", "/api/v1/conversations/{conversation_uid}/mute")],
248
+ params={"conversation_uid": arg(help="Conversation UID to unmute")},
249
+ )
250
+ async def unmute_conversation(
251
+ client: TetraClient, conversation_uid: str,
252
+ ) -> dict[str, Any]:
253
+ """Unmute a conversation for the caller.
254
+
255
+ Args:
256
+ client: Authenticated TetraClient.
257
+ conversation_uid: Conversation UID to unmute.
258
+
259
+ Returns:
260
+ Dict with ``is_muted: False``.
261
+ """
262
+ return await client.delete(f"/api/v1/conversations/{conversation_uid}/mute")
263
+
264
+
265
+ @operation(
266
+ cli="conversations dm",
267
+ summary="Find or preview a direct-message conversation with an account.",
268
+ covers=[("POST", "/api/v1/conversations/dm/{account_uid}")],
269
+ params={"account_uid": arg(help="Other account's UID")},
270
+ )
271
+ async def find_or_create_dm(
272
+ client: TetraClient, account_uid: str,
273
+ ) -> dict[str, Any]:
274
+ """Find an existing DM, or return an ephemeral preview if none exists.
275
+
276
+ No DB row is created until the first message is sent; the preview carries
277
+ enough participant info to render the chat header.
278
+
279
+ Args:
280
+ client: Authenticated TetraClient.
281
+ account_uid: The other participant's account UID.
282
+
283
+ Returns:
284
+ DM detail dict (``is_ephemeral`` True when no row exists yet).
285
+ """
286
+ return await client.post(f"/api/v1/conversations/dm/{account_uid}")
287
+
288
+
289
+ @operation(
290
+ cli="conversations group",
291
+ summary="Find or preview a group conversation for a group.",
292
+ covers=[("POST", "/api/v1/conversations/group/{group_uid}")],
293
+ params={"group_uid": arg(help="Group UID")},
294
+ )
295
+ async def find_or_create_group_conversation(
296
+ client: TetraClient, group_uid: str,
297
+ ) -> dict[str, Any]:
298
+ """Find an existing group chat, or return an ephemeral preview if none.
299
+
300
+ As with DMs, no DB row is created until the first message is sent.
301
+
302
+ Args:
303
+ client: Authenticated TetraClient.
304
+ group_uid: The group's UID.
305
+
306
+ Returns:
307
+ Conversation dict (``is_ephemeral`` True when no row exists yet).
308
+ """
309
+ return await client.post(f"/api/v1/conversations/group/{group_uid}")
310
+
311
+
312
+ # ---------------------------------------------------------------------------
313
+ # AI-thread convenience ops — resolve the caller's AI conversation, then act.
314
+ # These keep the historic ``messages read/post/reply/action`` ergonomics on the
315
+ # generated surface so an agent never has to fetch the AI conversation UID by
316
+ # hand. Each delegates to the route-level op above (and thus inherits coverage).
317
+ # ---------------------------------------------------------------------------
318
+
319
+ @operation(
320
+ cli="conversations ai-read",
321
+ summary="Read the caller's AI thread (unseen by default, or full history with --all).",
322
+ covers=[
323
+ ("GET", "/api/v1/conversations/ai"),
324
+ ("GET", "/api/v1/conversations/{conversation_uid}/agent/unseen"),
325
+ ("GET", "/api/v1/conversations/{conversation_uid}/messages"),
326
+ ],
327
+ params={
328
+ "all_": opt("--all", help="Show recent history instead of just unseen."),
329
+ "limit": opt("--limit", help="Max messages."),
330
+ },
331
+ )
332
+ async def ai_read(
333
+ client: TetraClient,
334
+ *,
335
+ all_: bool = False,
336
+ limit: int = 50,
337
+ ) -> dict[str, Any]:
338
+ """Read the caller's AI thread, resolving the AI conversation first.
339
+
340
+ Args:
341
+ client: Authenticated TetraClient.
342
+ all_: When True, return recent history; otherwise just unseen messages.
343
+ limit: Maximum number of messages to return.
344
+
345
+ Returns:
346
+ The messages payload (history or unseen) for the AI conversation.
347
+ """
348
+ convo = await get_ai_conversation(client)
349
+ if all_:
350
+ return await get_messages(client, convo["uid"], limit=limit)
351
+ return await get_unseen(client, convo["uid"], limit=limit)
352
+
353
+
354
+ @operation(
355
+ cli="conversations ai-post",
356
+ summary="Post a plain message to the caller's AI thread.",
357
+ covers=[
358
+ ("GET", "/api/v1/conversations/ai"),
359
+ ("POST", "/api/v1/conversations/{conversation_uid}/messages"),
360
+ ],
361
+ params={"content": arg(help="Message text.")},
362
+ )
363
+ async def ai_post(client: TetraClient, content: str) -> dict[str, Any]:
364
+ """Post a plain message to the caller's AI thread.
365
+
366
+ Args:
367
+ client: Authenticated TetraClient.
368
+ content: The message text to post.
369
+
370
+ Returns:
371
+ The created interaction dict.
372
+ """
373
+ convo = await get_ai_conversation(client)
374
+ return await post_message(client, convo["uid"], content=content)
375
+
376
+
377
+ @operation(
378
+ cli="conversations ai-reply",
379
+ summary="Post a quoted reply to a message in the caller's AI thread.",
380
+ covers=[
381
+ ("GET", "/api/v1/conversations/ai"),
382
+ ("POST", "/api/v1/conversations/{conversation_uid}/messages"),
383
+ ],
384
+ params={
385
+ "interaction_uid": arg(help="Interaction UID to reply to."),
386
+ "content": arg(help="Reply text."),
387
+ },
388
+ )
389
+ async def ai_reply(
390
+ client: TetraClient, interaction_uid: str, content: str,
391
+ ) -> dict[str, Any]:
392
+ """Post a quoted reply to a specific message/action in the AI thread.
393
+
394
+ Args:
395
+ client: Authenticated TetraClient.
396
+ interaction_uid: The interaction being replied to.
397
+ content: The reply text.
398
+
399
+ Returns:
400
+ The created interaction dict.
401
+ """
402
+ convo = await get_ai_conversation(client)
403
+ return await post_message(
404
+ client, convo["uid"], content=content, reply_to_uid=interaction_uid,
405
+ )
406
+
407
+
408
+ @operation(
409
+ cli="conversations ai-action",
410
+ summary="Log a structured action report to the caller's AI thread.",
411
+ covers=[
412
+ ("GET", "/api/v1/conversations/ai"),
413
+ ("POST", "/api/v1/conversations/{conversation_uid}/messages"),
414
+ ],
415
+ params={
416
+ "content": opt("--content", help="One-line human-readable summary."),
417
+ "action": opt("--action", json=True,
418
+ help="ActionPayload as a JSON object (action_type, subject_type, ...)."),
419
+ "reason": opt("--reason", help="Why the action was taken."),
420
+ "reply_to_uid": opt("--reply-to", help="Interaction UID this answers."),
421
+ },
422
+ )
423
+ async def ai_action(
424
+ client: TetraClient,
425
+ *,
426
+ content: str,
427
+ action: dict[str, Any],
428
+ reason: str | None = None,
429
+ reply_to_uid: str | None = None,
430
+ ) -> dict[str, Any]:
431
+ """Log a structured action report to the caller's AI thread.
432
+
433
+ Args:
434
+ client: Authenticated TetraClient.
435
+ content: One-line human-readable summary.
436
+ action: ActionPayload dict (action_type, subject_type, subject_uid, ...).
437
+ reason: Optional reason merged into ``action['reason']``.
438
+ reply_to_uid: Optional quoted-reply target interaction UID.
439
+
440
+ Returns:
441
+ The created interaction dict.
442
+ """
443
+ convo = await get_ai_conversation(client)
444
+ return await post_action(
445
+ client, convo["uid"], content=content, action=action,
446
+ reason=reason, reply_to_uid=reply_to_uid,
447
+ )