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,250 @@
|
|
|
1
|
+
"""Pure async operations for the data export/import API.
|
|
2
|
+
|
|
3
|
+
These ops mirror the backend ``/api/v1/data`` routes (see
|
|
4
|
+
``backend/tetra/api/routes/data.py``). Three of them — ``preview``,
|
|
5
|
+
``import`` and ``import-mapped`` — accept a CSV/zip file as a
|
|
6
|
+
``multipart/form-data`` upload, which the plain JSON-only ``TetraClient``
|
|
7
|
+
methods (``get``/``post``/…) cannot express. For those we go through the
|
|
8
|
+
shared ``_upload`` helper below, which drives the client's underlying httpx
|
|
9
|
+
``AsyncClient`` with ``files=``/``data=`` and routes the response through the
|
|
10
|
+
same ``_handle_response`` normalizer every other op relies on. The helper is a
|
|
11
|
+
module-level function so unit tests can monkeypatch it and assert the exact
|
|
12
|
+
multipart payload (method, path, file name/bytes, form fields) without a live
|
|
13
|
+
backend.
|
|
14
|
+
|
|
15
|
+
The remaining routes (``fields``, ``fix-goal-statuses``) carry no body and use
|
|
16
|
+
the ordinary client methods directly.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json as _json
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from tetra_cli.api_client import TetraClient
|
|
25
|
+
from tetra_cli.api_client.client import _handle_response
|
|
26
|
+
from tetra_cli.registry import arg, operation, opt
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def _upload(
|
|
30
|
+
client: TetraClient,
|
|
31
|
+
path: str,
|
|
32
|
+
*,
|
|
33
|
+
file_field: str,
|
|
34
|
+
file_name: str,
|
|
35
|
+
file_bytes: bytes,
|
|
36
|
+
content_type: str,
|
|
37
|
+
data: dict[str, Any] | None = None,
|
|
38
|
+
) -> Any:
|
|
39
|
+
"""POST a ``multipart/form-data`` request through the client's httpx layer.
|
|
40
|
+
|
|
41
|
+
The JSON-oriented ``TetraClient.post`` only sends ``application/json``, so
|
|
42
|
+
file-upload routes can't use it. This helper reaches the client's
|
|
43
|
+
underlying ``AsyncClient`` (carrying the same auth + ``X-Tetra-Agent``
|
|
44
|
+
headers) to send a real multipart body, then normalizes the response with
|
|
45
|
+
the shared ``_handle_response`` so error handling matches every other op.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
client: Authenticated TetraClient.
|
|
49
|
+
path: API path (e.g. ``/api/v1/data/import``).
|
|
50
|
+
file_field: The multipart field name the route expects (``file``).
|
|
51
|
+
file_name: File name to send (used by the server's extension check).
|
|
52
|
+
file_bytes: Raw file contents.
|
|
53
|
+
content_type: MIME type for the file part.
|
|
54
|
+
data: Extra form fields (e.g. ``mapping`` JSON, ``sample_size``).
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Parsed JSON response (or normalized error via ``_handle_response``).
|
|
58
|
+
"""
|
|
59
|
+
files = {file_field: (file_name, file_bytes, content_type)}
|
|
60
|
+
response = await client._client.post(path, files=files, data=data or {})
|
|
61
|
+
return _handle_response(response)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _read_file(file: str) -> tuple[str, bytes]:
|
|
65
|
+
"""Read a local file, returning its base name and raw bytes.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
file: Path to the local file to upload.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
``(file_name, file_bytes)``.
|
|
72
|
+
"""
|
|
73
|
+
p = Path(file)
|
|
74
|
+
return p.name, p.read_bytes()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@operation(
|
|
78
|
+
cli="data fields",
|
|
79
|
+
summary="List importable Tetra fields and the transforms available for mapping.",
|
|
80
|
+
covers=[("GET", "/api/v1/data/fields")],
|
|
81
|
+
)
|
|
82
|
+
async def get_field_definitions(client: TetraClient) -> dict[str, Any]:
|
|
83
|
+
"""List the Tetra fields and transforms available for building a mapping.
|
|
84
|
+
|
|
85
|
+
Returns the same ``fields``/``transforms`` catalog the mapped-import flow
|
|
86
|
+
validates against, so an agent can construct a valid ``--mapping`` for
|
|
87
|
+
``data import-mapped`` without guessing field or transform names.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
client: Authenticated TetraClient.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dict with ``fields`` (per entity-type field defs) and ``transforms``
|
|
94
|
+
(transform name -> docstring).
|
|
95
|
+
"""
|
|
96
|
+
return await client.get("/api/v1/data/fields")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@operation(
|
|
100
|
+
cli="data fix-goal-statuses",
|
|
101
|
+
summary="Transition CREATED goals that have events to IN_PROGRESS.",
|
|
102
|
+
covers=[("POST", "/api/v1/data/fix-goal-statuses")],
|
|
103
|
+
)
|
|
104
|
+
async def fix_goal_statuses(client: TetraClient) -> dict[str, Any]:
|
|
105
|
+
"""Repair goal statuses: move CREATED goals with events to IN_PROGRESS.
|
|
106
|
+
|
|
107
|
+
A maintenance/no-body endpoint. Useful after a bulk import (or to fix
|
|
108
|
+
legacy data) where goals were never transitioned when their events landed.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
client: Authenticated TetraClient.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Detailed report dict: ``total_goals``, ``total_events``,
|
|
115
|
+
``goals_transitioned`` (list of names), ``transitioned_count``, etc.
|
|
116
|
+
"""
|
|
117
|
+
return await client.post("/api/v1/data/fix-goal-statuses")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@operation(
|
|
121
|
+
cli="data preview",
|
|
122
|
+
summary="Preview a CSV file's columns, sample rows, and mappable Tetra fields.",
|
|
123
|
+
covers=[("POST", "/api/v1/data/preview")],
|
|
124
|
+
params={
|
|
125
|
+
"file": arg(help="Path to the CSV file to preview"),
|
|
126
|
+
"sample_size": opt("--sample-size", min=1,
|
|
127
|
+
help="Number of sample rows to return (default 5)"),
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
async def preview_file(
|
|
131
|
+
client: TetraClient,
|
|
132
|
+
file: str,
|
|
133
|
+
*,
|
|
134
|
+
sample_size: int = 5,
|
|
135
|
+
) -> dict[str, Any]:
|
|
136
|
+
"""Preview a CSV file's structure to plan a mapped import.
|
|
137
|
+
|
|
138
|
+
Uploads the CSV (multipart) and returns its column headers, a handful of
|
|
139
|
+
sample rows, the row count, and the catalog of mappable Tetra fields and
|
|
140
|
+
transforms — everything needed to author a ``--mapping`` for
|
|
141
|
+
``data import-mapped``.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
client: Authenticated TetraClient.
|
|
145
|
+
file: Path to a local ``.csv`` file.
|
|
146
|
+
sample_size: How many sample rows to include (default 5).
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Dict with ``filename``, ``columns``, ``sample_rows``, ``row_count``,
|
|
150
|
+
``tetra_fields`` and ``available_transforms``.
|
|
151
|
+
"""
|
|
152
|
+
file_name, file_bytes = _read_file(file)
|
|
153
|
+
return await _upload(
|
|
154
|
+
client,
|
|
155
|
+
"/api/v1/data/preview",
|
|
156
|
+
file_field="file",
|
|
157
|
+
file_name=file_name,
|
|
158
|
+
file_bytes=file_bytes,
|
|
159
|
+
content_type="text/csv",
|
|
160
|
+
data={"sample_size": str(sample_size)},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@operation(
|
|
165
|
+
cli="data import",
|
|
166
|
+
summary="Import a Tetra export zip (values, goals, events CSVs).",
|
|
167
|
+
covers=[("POST", "/api/v1/data/import")],
|
|
168
|
+
params={
|
|
169
|
+
"file": arg(help="Path to the .zip export archive to import"),
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
async def import_data(
|
|
173
|
+
client: TetraClient,
|
|
174
|
+
file: str,
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
"""Import a full Tetra export: a ``.zip`` of values/goals/events CSVs.
|
|
177
|
+
|
|
178
|
+
Mirrors the export format produced by ``data export``. The server imports
|
|
179
|
+
in dependency order (values -> goals -> events), skips entities that
|
|
180
|
+
already exist, transitions goals with events to IN_PROGRESS, and awards
|
|
181
|
+
retroactive XP. Only ``.zip`` archives are accepted.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
client: Authenticated TetraClient.
|
|
185
|
+
file: Path to a local ``.zip`` archive.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dict with per-entity ``imported`` counts, optional ``skipped`` counts,
|
|
189
|
+
``errors`` (or None), and ``success``.
|
|
190
|
+
"""
|
|
191
|
+
file_name, file_bytes = _read_file(file)
|
|
192
|
+
return await _upload(
|
|
193
|
+
client,
|
|
194
|
+
"/api/v1/data/import",
|
|
195
|
+
file_field="file",
|
|
196
|
+
file_name=file_name,
|
|
197
|
+
file_bytes=file_bytes,
|
|
198
|
+
content_type="application/zip",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@operation(
|
|
203
|
+
cli="data import-mapped",
|
|
204
|
+
summary="Import a single CSV using a custom column->field mapping.",
|
|
205
|
+
covers=[("POST", "/api/v1/data/import-mapped")],
|
|
206
|
+
params={
|
|
207
|
+
"file": arg(help="Path to the CSV file to import"),
|
|
208
|
+
"mapping": opt("--mapping", json=True,
|
|
209
|
+
help=("MappingConfig as a JSON dict: entity_type, "
|
|
210
|
+
"column_mapping, and optional transforms/defaults/"
|
|
211
|
+
"value_maps/skip_if_not_empty/sign_modifiers.")),
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
async def import_mapped(
|
|
215
|
+
client: TetraClient,
|
|
216
|
+
file: str,
|
|
217
|
+
*,
|
|
218
|
+
mapping: dict[str, Any],
|
|
219
|
+
) -> dict[str, Any]:
|
|
220
|
+
"""Import one CSV into a chosen entity type via an explicit column mapping.
|
|
221
|
+
|
|
222
|
+
The backend expects ``mapping`` as a JSON string form field alongside the
|
|
223
|
+
file; this op accepts the mapping as a dict (the CLI parses the JSON blob)
|
|
224
|
+
and serializes it to JSON for the multipart ``mapping`` field. The dict
|
|
225
|
+
must carry at least ``entity_type`` ('values' | 'goals' | 'events') and
|
|
226
|
+
``column_mapping`` ({source column -> tetra field(s)}); ``transforms``,
|
|
227
|
+
``defaults``, ``value_maps``, ``skip_if_not_empty`` and ``sign_modifiers``
|
|
228
|
+
are optional. Use ``data fields`` / ``data preview`` to discover valid
|
|
229
|
+
field and transform names.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
client: Authenticated TetraClient.
|
|
233
|
+
file: Path to a local ``.csv`` file.
|
|
234
|
+
mapping: MappingConfig dict (see above).
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Import result dict: ``imported``, ``total_rows``, ``errors`` (or None),
|
|
238
|
+
``success`` and, when relevant, ``skipped`` / ``goals_transitioned`` /
|
|
239
|
+
XP fields.
|
|
240
|
+
"""
|
|
241
|
+
file_name, file_bytes = _read_file(file)
|
|
242
|
+
return await _upload(
|
|
243
|
+
client,
|
|
244
|
+
"/api/v1/data/import-mapped",
|
|
245
|
+
file_field="file",
|
|
246
|
+
file_name=file_name,
|
|
247
|
+
file_bytes=file_bytes,
|
|
248
|
+
content_type="text/csv",
|
|
249
|
+
data={"mapping": _json.dumps(mapping)},
|
|
250
|
+
)
|