maitai-cli 0.1.1__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.
maitai_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Maitai CLI - Command-line interface for the Maitai Platform Developer API."""
2
+
3
+ __version__ = "0.1.1"
maitai_cli/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Allow running as python -m maitai_cli."""
2
+
3
+ from maitai_cli.main import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
maitai_cli/api.py ADDED
@@ -0,0 +1,90 @@
1
+ """
2
+ API client for the Maitai Platform Developer API (api/v1).
3
+
4
+ API documentation: docs/internal and docs/external in the maitai-mono repo.
5
+ Base URL: https://api.trymaitai.ai/api/v1
6
+ Authentication: X-Maitai-Api-Key header (create keys in Portal → Settings → API Keys).
7
+ """
8
+
9
+ from typing import Any
10
+
11
+ import httpx
12
+ from maitai_cli.config import get_api_key, get_base_url
13
+
14
+
15
+ class APIError(Exception):
16
+ """Raised when an API request fails."""
17
+
18
+ def __init__(self, status_code: int, message: str, details: dict | None = None):
19
+ self.status_code = status_code
20
+ self.message = message
21
+ self.details = details or {}
22
+ super().__init__(f"{status_code}: {message}")
23
+
24
+
25
+ class APIClient:
26
+ """HTTP client for the Maitai Developer API."""
27
+
28
+ def __init__(self, api_key: str | None = None, base_url: str | None = None):
29
+ self.api_key = api_key or get_api_key()
30
+ self.base_url = (base_url or get_base_url()).rstrip("/")
31
+ self._client: httpx.Client | None = None
32
+
33
+ def _get_client(self) -> httpx.Client:
34
+ if self._client is None:
35
+ headers = {}
36
+ if self.api_key:
37
+ headers["X-Maitai-Api-Key"] = self.api_key
38
+ self._client = httpx.Client(
39
+ base_url=self.base_url,
40
+ headers=headers,
41
+ timeout=30.0,
42
+ )
43
+ return self._client
44
+
45
+ def close(self) -> None:
46
+ """Close the HTTP client."""
47
+ if self._client:
48
+ self._client.close()
49
+ self._client = None
50
+
51
+ def _request(
52
+ self,
53
+ method: str,
54
+ path: str,
55
+ *,
56
+ params: dict | None = None,
57
+ json: dict | None = None,
58
+ ) -> dict[str, Any]:
59
+ """Make an API request and return the parsed JSON response."""
60
+ if not self.api_key:
61
+ raise APIError(
62
+ 401, "Not authenticated. Run 'maitai login' or set MAITAI_API_KEY."
63
+ )
64
+ client = self._get_client()
65
+ path = path if path.startswith("/") else f"/{path}"
66
+ r = client.request(method, path, params=params, json=json)
67
+ try:
68
+ body = r.json()
69
+ except Exception:
70
+ body = {}
71
+ if r.status_code >= 400:
72
+ err = body.get("error", {})
73
+ raise APIError(
74
+ r.status_code,
75
+ err.get("message", r.text or str(r.status_code)),
76
+ err.get("details"),
77
+ )
78
+ return body
79
+
80
+ def get(self, path: str, params: dict | None = None) -> dict[str, Any]:
81
+ return self._request("GET", path, params=params)
82
+
83
+ def post(self, path: str, json: dict | None = None) -> dict[str, Any]:
84
+ return self._request("POST", path, json=json)
85
+
86
+ def put(self, path: str, json: dict | None = None) -> dict[str, Any]:
87
+ return self._request("PUT", path, json=json)
88
+
89
+ def delete(self, path: str) -> dict[str, Any]:
90
+ return self._request("DELETE", path)
@@ -0,0 +1,457 @@
1
+ """Generated CLI commands from OpenAPI spec. Do not edit by hand."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from maitai_cli.utils import console, get_client, handle_api_errors, print_json
8
+
9
+ @handle_api_errors
10
+ def _agents(
11
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List agents"),
12
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
13
+ create_body: Optional[str] = typer.Option(None, "--create", "-c", help="Create (JSON body)"),
14
+ update_id: Optional[int] = typer.Option(None, "--update", "-u", help="Update by ID"),
15
+ delete_id: Optional[int] = typer.Option(None, "--delete", "-d", help="Delete by ID"),
16
+ include_subagents: Optional[bool] = typer.Option(None, "--include-subagents", help="Include sub-agents in the response."),
17
+ include_actions: Optional[bool] = typer.Option(None, "--include-actions", help="Include agent actions in the response."),
18
+ detailed: Optional[bool] = typer.Option(None, "--detailed", help="Include full configuration details in results."),
19
+ page: Optional[int] = typer.Option(None, "--page", help="Page number (1-indexed) for page-based pagination."),
20
+ page_size: Optional[int] = typer.Option(None, "--page-size", help="Number of items per page."),
21
+ version: Optional[str] = typer.Option(None, "--version", help="Filter by version string."),
22
+ ):
23
+ """/agents - generated from OpenAPI."""
24
+ client = get_client()
25
+ params: dict = {}
26
+ if include_subagents is not None:
27
+ params["include_subagents"] = include_subagents
28
+ if include_actions is not None:
29
+ params["include_actions"] = include_actions
30
+ if detailed is not None:
31
+ params["detailed"] = detailed
32
+ if page is not None:
33
+ params["page"] = page
34
+ if page_size is not None:
35
+ params["page_size"] = page_size
36
+ if version is not None:
37
+ params["version"] = version
38
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
39
+ r = client.get("/agents", params=params)
40
+ print_json(r.get("data", []), r.get("pagination"))
41
+ return
42
+ if get_id is not None:
43
+ r = client.get(f"/agents/{get_id}", params=params)
44
+ print_json(r.get("data"))
45
+ return
46
+ if create_body:
47
+ import json
48
+ body = json.loads(create_body)
49
+ r = client.post("/agents", json=body)
50
+ print_json(r.get("data"))
51
+ return
52
+ if update_id is not None:
53
+ import json
54
+ body = json.loads(typer.prompt("JSON body for update") or "{}")
55
+ r = client.put(f"/agents/{update_id}", json=body)
56
+ print_json(r.get("data"))
57
+ return
58
+ if delete_id is not None:
59
+ client.delete(f"/agents/{delete_id}")
60
+ console.print("[green]Deleted.[/green]")
61
+ return
62
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
63
+
64
+ @handle_api_errors
65
+ def _applications(
66
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List applications"),
67
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
68
+ create_body: Optional[str] = typer.Option(None, "--create", "-c", help="Create (JSON body)"),
69
+ update_id: Optional[int] = typer.Option(None, "--update", "-u", help="Update by ID"),
70
+ delete_id: Optional[int] = typer.Option(None, "--delete", "-d", help="Delete by ID"),
71
+ detailed: bool = typer.Option(False, "--detailed", help="Include full config"),
72
+ ):
73
+ """/applications - generated from OpenAPI."""
74
+ client = get_client()
75
+ params: dict = {}
76
+ if detailed is not None:
77
+ params["detailed"] = detailed
78
+ if detailed:
79
+ params["detailed"] = "true"
80
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
81
+ r = client.get("/applications", params=params)
82
+ print_json(r.get("data", []), r.get("pagination"))
83
+ return
84
+ if get_id is not None:
85
+ r = client.get(f"/applications/{get_id}", params=params)
86
+ print_json(r.get("data"))
87
+ return
88
+ if create_body:
89
+ import json
90
+ body = json.loads(create_body)
91
+ r = client.post("/applications", json=body)
92
+ print_json(r.get("data"))
93
+ return
94
+ if update_id is not None:
95
+ import json
96
+ body = json.loads(typer.prompt("JSON body for update") or "{}")
97
+ r = client.put(f"/applications/{update_id}", json=body)
98
+ print_json(r.get("data"))
99
+ return
100
+ if delete_id is not None:
101
+ client.delete(f"/applications/{delete_id}")
102
+ console.print("[green]Deleted.[/green]")
103
+ return
104
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
105
+
106
+ @handle_api_errors
107
+ def _conversation_trees(
108
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List conversation trees"),
109
+ get_id: Optional[str] = typer.Option(None, "--get", "-g", help="Get by ID"),
110
+ delete_id: Optional[str] = typer.Option(None, "--delete", "-d", help="Delete by ID"),
111
+ application_id: Optional[int] = typer.Option(None, "--application-id", help="Filter by application ID."),
112
+ ):
113
+ """/conversation-trees - generated from OpenAPI."""
114
+ client = get_client()
115
+ params: dict = {}
116
+ if application_id is not None:
117
+ params["application_id"] = application_id
118
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
119
+ r = client.get("/conversation-trees", params=params)
120
+ print_json(r.get("data", []), r.get("pagination"))
121
+ return
122
+ if get_id is not None:
123
+ r = client.get(f"/conversation-trees/{get_id}", params=params)
124
+ print_json(r.get("data"))
125
+ return
126
+ if delete_id is not None:
127
+ client.delete(f"/conversation-trees/{delete_id}")
128
+ console.print("[green]Deleted.[/green]")
129
+ return
130
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
131
+
132
+ @handle_api_errors
133
+ def _datasets(
134
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List datasets"),
135
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
136
+ create_body: Optional[str] = typer.Option(None, "--create", "-c", help="Create (JSON body)"),
137
+ update_id: Optional[int] = typer.Option(None, "--update", "-u", help="Update by ID"),
138
+ delete_id: Optional[int] = typer.Option(None, "--delete", "-d", help="Delete by ID"),
139
+ ):
140
+ """/datasets - generated from OpenAPI."""
141
+ client = get_client()
142
+ params: dict = {}
143
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
144
+ r = client.get("/datasets", params=params)
145
+ print_json(r.get("data", []), r.get("pagination"))
146
+ return
147
+ if get_id is not None:
148
+ r = client.get(f"/datasets/{get_id}", params=params)
149
+ print_json(r.get("data"))
150
+ return
151
+ if create_body:
152
+ import json
153
+ body = json.loads(create_body)
154
+ r = client.post("/datasets", json=body)
155
+ print_json(r.get("data"))
156
+ return
157
+ if update_id is not None:
158
+ import json
159
+ body = json.loads(typer.prompt("JSON body for update") or "{}")
160
+ r = client.put(f"/datasets/{update_id}", json=body)
161
+ print_json(r.get("data"))
162
+ return
163
+ if delete_id is not None:
164
+ client.delete(f"/datasets/{delete_id}")
165
+ console.print("[green]Deleted.[/green]")
166
+ return
167
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
168
+
169
+ @handle_api_errors
170
+ def _evaluations(
171
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List evaluations"),
172
+ get_id: Optional[str] = typer.Option(None, "--get", "-g", help="Get by ID"),
173
+ create_body: Optional[str] = typer.Option(None, "--create", "-c", help="Create (JSON body)"),
174
+ full: Optional[bool] = typer.Option(None, "--full", help="Include full evaluation details."),
175
+ ):
176
+ """/evaluations - generated from OpenAPI."""
177
+ client = get_client()
178
+ params: dict = {}
179
+ if full is not None:
180
+ params["full"] = full
181
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
182
+ r = client.get("/evaluations", params=params)
183
+ print_json(r.get("data", []), r.get("pagination"))
184
+ return
185
+ if get_id is not None:
186
+ r = client.get(f"/evaluations/{get_id}", params=params)
187
+ print_json(r.get("data"))
188
+ return
189
+ if create_body:
190
+ import json
191
+ body = json.loads(create_body)
192
+ r = client.post("/evaluations", json=body)
193
+ print_json(r.get("data"))
194
+ return
195
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
196
+
197
+ @handle_api_errors
198
+ def _finetune_runs(
199
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List finetune runs"),
200
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
201
+ create_body: Optional[str] = typer.Option(None, "--create", "-c", help="Create (JSON body)"),
202
+ status: Optional[str] = typer.Option(None, "--status", help="Filter by status (can be specified multiple times)"),
203
+ ):
204
+ """/finetune-runs - generated from OpenAPI."""
205
+ client = get_client()
206
+ params: dict = {}
207
+ if status is not None:
208
+ params["status"] = status
209
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
210
+ r = client.get("/finetune-runs", params=params)
211
+ print_json(r.get("data", []), r.get("pagination"))
212
+ return
213
+ if get_id is not None:
214
+ r = client.get(f"/finetune-runs/{get_id}", params=params)
215
+ print_json(r.get("data"))
216
+ return
217
+ if create_body:
218
+ import json
219
+ body = json.loads(create_body)
220
+ r = client.post("/finetune-runs", json=body)
221
+ print_json(r.get("data"))
222
+ return
223
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
224
+
225
+ @handle_api_errors
226
+ def _intent_groups(
227
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List intent groups"),
228
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
229
+ detailed: Optional[bool] = typer.Option(None, "--detailed", help="Include full configuration details in results."),
230
+ start_date: Optional[int] = typer.Option(None, "--start-date", help="Start of date range as Unix timestamp (millisecond"),
231
+ end_date: Optional[int] = typer.Option(None, "--end-date", help="End of date range as Unix timestamp (milliseconds)"),
232
+ include_messages: Optional[bool] = typer.Option(None, "--include-messages", help="Include full message history in results."),
233
+ ):
234
+ """/intent-groups - generated from OpenAPI."""
235
+ client = get_client()
236
+ params: dict = {}
237
+ if detailed is not None:
238
+ params["detailed"] = detailed
239
+ if start_date is not None:
240
+ params["start_date"] = start_date
241
+ if end_date is not None:
242
+ params["end_date"] = end_date
243
+ if include_messages is not None:
244
+ params["include_messages"] = include_messages
245
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
246
+ r = client.get("/intent-groups", params=params)
247
+ print_json(r.get("data", []), r.get("pagination"))
248
+ return
249
+ if get_id is not None:
250
+ r = client.get(f"/intent-groups/{get_id}", params=params)
251
+ print_json(r.get("data"))
252
+ return
253
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
254
+
255
+ @handle_api_errors
256
+ def _models(
257
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List models"),
258
+ model_type: Optional[str] = typer.Option(None, "--model-type", help="Filter by model type (can be specified multiple ti"),
259
+ available: bool = typer.Option(False, "--available", "-a", help="List available base models"),
260
+ ):
261
+ """/models - generated from OpenAPI."""
262
+ client = get_client()
263
+ params: dict = {}
264
+ if model_type is not None:
265
+ params["model_type"] = model_type
266
+ if available:
267
+ r = client.get("/models/available")
268
+ print_json(r.get("data", []))
269
+ return
270
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
271
+ r = client.get("/models", params=params)
272
+ print_json(r.get("data", []), r.get("pagination"))
273
+ return
274
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
275
+
276
+ @handle_api_errors
277
+ def _requests(
278
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List requests"),
279
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
280
+ application_id: Optional[int] = typer.Option(None, "--application-id", help="Filter by application ID."),
281
+ intent_id: Optional[int] = typer.Option(None, "--intent-id", help="Filter by intent ID."),
282
+ start_date: Optional[int] = typer.Option(None, "--start-date", help="Start of date range as Unix timestamp (millisecond"),
283
+ end_date: Optional[int] = typer.Option(None, "--end-date", help="End of date range as Unix timestamp (milliseconds)"),
284
+ include_messages: Optional[bool] = typer.Option(None, "--include-messages", help="Include full message history in results."),
285
+ ):
286
+ """/requests - generated from OpenAPI."""
287
+ client = get_client()
288
+ params: dict = {}
289
+ if application_id is not None:
290
+ params["application_id"] = application_id
291
+ if intent_id is not None:
292
+ params["intent_id"] = intent_id
293
+ if start_date is not None:
294
+ params["start_date"] = start_date
295
+ if end_date is not None:
296
+ params["end_date"] = end_date
297
+ if include_messages is not None:
298
+ params["include_messages"] = include_messages
299
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
300
+ r = client.get("/requests", params=params)
301
+ print_json(r.get("data", []), r.get("pagination"))
302
+ return
303
+ if get_id is not None:
304
+ r = client.get(f"/requests/{get_id}", params=params)
305
+ print_json(r.get("data"))
306
+ return
307
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
308
+
309
+ @handle_api_errors
310
+ def _sentinels(
311
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List sentinels"),
312
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
313
+ create_body: Optional[str] = typer.Option(None, "--create", "-c", help="Create (JSON body)"),
314
+ update_id: Optional[int] = typer.Option(None, "--update", "-u", help="Update by ID"),
315
+ delete_id: Optional[int] = typer.Option(None, "--delete", "-d", help="Delete by ID"),
316
+ ):
317
+ """/sentinels - generated from OpenAPI."""
318
+ client = get_client()
319
+ params: dict = {}
320
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
321
+ r = client.get("/sentinels", params=params)
322
+ print_json(r.get("data", []), r.get("pagination"))
323
+ return
324
+ if get_id is not None:
325
+ r = client.get(f"/sentinels/{get_id}", params=params)
326
+ print_json(r.get("data"))
327
+ return
328
+ if create_body:
329
+ import json
330
+ body = json.loads(create_body)
331
+ r = client.post("/sentinels", json=body)
332
+ print_json(r.get("data"))
333
+ return
334
+ if update_id is not None:
335
+ import json
336
+ body = json.loads(typer.prompt("JSON body for update") or "{}")
337
+ r = client.put(f"/sentinels/{update_id}", json=body)
338
+ print_json(r.get("data"))
339
+ return
340
+ if delete_id is not None:
341
+ client.delete(f"/sentinels/{delete_id}")
342
+ console.print("[green]Deleted.[/green]")
343
+ return
344
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
345
+
346
+ @handle_api_errors
347
+ def _sessions(
348
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List sessions"),
349
+ get_id: Optional[str] = typer.Option(None, "--get", "-g", help="Get by ID"),
350
+ application_id: Optional[int] = typer.Option(None, "--application-id", help="Filter by application ID."),
351
+ ):
352
+ """/sessions - generated from OpenAPI."""
353
+ client = get_client()
354
+ params: dict = {}
355
+ if application_id is not None:
356
+ params["application_id"] = application_id
357
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
358
+ r = client.get("/sessions", params=params)
359
+ print_json(r.get("data", []), r.get("pagination"))
360
+ return
361
+ if get_id is not None:
362
+ r = client.get(f"/sessions/{get_id}", params=params)
363
+ print_json(r.get("data"))
364
+ return
365
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
366
+
367
+ @handle_api_errors
368
+ def _test_runs(
369
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List test runs"),
370
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
371
+ create_body: Optional[str] = typer.Option(None, "--create", "-c", help="Create (JSON body)"),
372
+ delete_id: Optional[int] = typer.Option(None, "--delete", "-d", help="Delete by ID"),
373
+ intent_group_id: Optional[int] = typer.Option(None, "--intent-group-id", help="intent_group_id"),
374
+ ):
375
+ """/test-runs - generated from OpenAPI."""
376
+ client = get_client()
377
+ params: dict = {}
378
+ if intent_group_id is not None:
379
+ params["intent_group_id"] = intent_group_id
380
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
381
+ r = client.get("/test-runs", params=params)
382
+ print_json(r.get("data", []), r.get("pagination"))
383
+ return
384
+ if get_id is not None:
385
+ r = client.get(f"/test-runs/{get_id}", params=params)
386
+ print_json(r.get("data"))
387
+ return
388
+ if create_body:
389
+ import json
390
+ body = json.loads(create_body)
391
+ r = client.post("/test-runs", json=body)
392
+ print_json(r.get("data"))
393
+ return
394
+ if delete_id is not None:
395
+ client.delete(f"/test-runs/{delete_id}")
396
+ console.print("[green]Deleted.[/green]")
397
+ return
398
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
399
+
400
+ @handle_api_errors
401
+ def _test_sets(
402
+ list_cmd: bool = typer.Option(False, "--list", "-l", help="List test sets"),
403
+ get_id: Optional[int] = typer.Option(None, "--get", "-g", help="Get by ID"),
404
+ create_body: Optional[str] = typer.Option(None, "--create", "-c", help="Create (JSON body)"),
405
+ update_id: Optional[int] = typer.Option(None, "--update", "-u", help="Update by ID"),
406
+ delete_id: Optional[int] = typer.Option(None, "--delete", "-d", help="Delete by ID"),
407
+ start_date: Optional[int] = typer.Option(None, "--start-date", help="Start of date range as Unix timestamp (millisecond"),
408
+ end_date: Optional[int] = typer.Option(None, "--end-date", help="End of date range as Unix timestamp (milliseconds)"),
409
+ ):
410
+ """/test-sets - generated from OpenAPI."""
411
+ client = get_client()
412
+ params: dict = {}
413
+ if start_date is not None:
414
+ params["start_date"] = start_date
415
+ if end_date is not None:
416
+ params["end_date"] = end_date
417
+ if list_cmd or (not get_id and not create_body and not update_id and not delete_id):
418
+ r = client.get("/test-sets", params=params)
419
+ print_json(r.get("data", []), r.get("pagination"))
420
+ return
421
+ if get_id is not None:
422
+ r = client.get(f"/test-sets/{get_id}", params=params)
423
+ print_json(r.get("data"))
424
+ return
425
+ if create_body:
426
+ import json
427
+ body = json.loads(create_body)
428
+ r = client.post("/test-sets", json=body)
429
+ print_json(r.get("data"))
430
+ return
431
+ if update_id is not None:
432
+ import json
433
+ body = json.loads(typer.prompt("JSON body for update") or "{}")
434
+ r = client.put(f"/test-sets/{update_id}", json=body)
435
+ print_json(r.get("data"))
436
+ return
437
+ if delete_id is not None:
438
+ client.delete(f"/test-sets/{delete_id}")
439
+ console.print("[green]Deleted.[/green]")
440
+ return
441
+ typer.echo("Use --list, --get ID, --create '{\"key\":\"val\"}', --update ID, or --delete ID")
442
+
443
+ def register_commands(typer_app: typer.Typer) -> None:
444
+ """Register all generated commands with the Typer app."""
445
+ typer_app.command(name="agents")(_agents)
446
+ typer_app.command(name="applications")(_applications)
447
+ typer_app.command(name="conversation-trees")(_conversation_trees)
448
+ typer_app.command(name="datasets")(_datasets)
449
+ typer_app.command(name="evaluations")(_evaluations)
450
+ typer_app.command(name="finetune-runs")(_finetune_runs)
451
+ typer_app.command(name="intent-groups")(_intent_groups)
452
+ typer_app.command(name="models")(_models)
453
+ typer_app.command(name="requests")(_requests)
454
+ typer_app.command(name="sentinels")(_sentinels)
455
+ typer_app.command(name="sessions")(_sessions)
456
+ typer_app.command(name="test-runs")(_test_runs)
457
+ typer_app.command(name="test-sets")(_test_sets)
maitai_cli/config.py ADDED
@@ -0,0 +1,74 @@
1
+ """Configuration and authentication for the Maitai CLI."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ CONFIG_DIR = Path.home() / ".maitai"
7
+ CONFIG_FILE = CONFIG_DIR / "config"
8
+ API_KEY_ENV = "MAITAI_API_KEY"
9
+ BASE_URL_ENV = "MAITAI_BASE_URL"
10
+ DEFAULT_BASE_URL = "https://api.trymaitai.ai/api/v1"
11
+
12
+
13
+ def get_config_dir() -> Path:
14
+ """Return the config directory, creating it if needed."""
15
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
16
+ return CONFIG_DIR
17
+
18
+
19
+ def get_api_key() -> str | None:
20
+ """Get API key from environment or config file."""
21
+ key = os.environ.get(API_KEY_ENV)
22
+ if key:
23
+ return key.strip()
24
+ if CONFIG_FILE.exists():
25
+ try:
26
+ content = CONFIG_FILE.read_text().strip()
27
+ for line in content.splitlines():
28
+ line = line.strip()
29
+ if line.startswith("api_key="):
30
+ return line.split("=", 1)[1].strip().strip('"').strip("'")
31
+ if line.startswith("MAITAI_API_KEY="):
32
+ return line.split("=", 1)[1].strip().strip('"').strip("'")
33
+ except OSError:
34
+ pass
35
+ return None
36
+
37
+
38
+ def get_base_url() -> str:
39
+ """Get base URL from environment or config file."""
40
+ url = os.environ.get(BASE_URL_ENV)
41
+ if url:
42
+ return url.strip().rstrip("/")
43
+ if CONFIG_FILE.exists():
44
+ try:
45
+ content = CONFIG_FILE.read_text().strip()
46
+ for line in content.splitlines():
47
+ line = line.strip()
48
+ if line.startswith("base_url="):
49
+ return (
50
+ line.split("=", 1)[1].strip().strip('"').strip("'").rstrip("/")
51
+ )
52
+ if line.startswith("MAITAI_BASE_URL="):
53
+ return (
54
+ line.split("=", 1)[1].strip().strip('"').strip("'").rstrip("/")
55
+ )
56
+ except OSError:
57
+ pass
58
+ return DEFAULT_BASE_URL
59
+
60
+
61
+ def save_config(api_key: str, base_url: str | None = None) -> None:
62
+ """Save API key and optional base URL to config file."""
63
+ get_config_dir()
64
+ url = base_url or get_base_url()
65
+ content = f"api_key={api_key}\nbase_url={url}\n"
66
+ CONFIG_FILE.write_text(content)
67
+ # Restrict permissions
68
+ os.chmod(CONFIG_FILE, 0o600)
69
+
70
+
71
+ def clear_config() -> None:
72
+ """Remove stored credentials."""
73
+ if CONFIG_FILE.exists():
74
+ CONFIG_FILE.unlink()
maitai_cli/main.py ADDED
@@ -0,0 +1,140 @@
1
+ """
2
+ Maitai CLI - Command-line interface for the Maitai Platform Developer API.
3
+
4
+ API docs: See docs/internal and docs/external in the maitai-mono repository.
5
+ """
6
+
7
+ from typing import Optional
8
+
9
+ import typer
10
+
11
+ # Import generated commands and register them
12
+ from maitai_cli.commands_generated import register_commands
13
+ from maitai_cli.config import clear_config, get_api_key, get_base_url, save_config
14
+ from maitai_cli.utils import console, get_client, handle_api_errors, print_json
15
+ from rich.console import Console
16
+
17
+ app = typer.Typer(
18
+ name="maitai",
19
+ help="Maitai Platform Developer API CLI (api/v1). API docs: docs/internal and docs/external in maitai-mono. Create API keys at https://portal.trymaitai.com → Settings → API Keys.",
20
+ no_args_is_help=True,
21
+ )
22
+
23
+ # Register generated resource commands
24
+ register_commands(app)
25
+
26
+
27
+ # ─── Auth (hand-written) ────────────────────────────────────────────────────
28
+
29
+
30
+ @app.command()
31
+ def login(
32
+ api_key: Optional[str] = typer.Option(
33
+ None,
34
+ "--api-key",
35
+ "-k",
36
+ help="Maitai API key (or set MAITAI_API_KEY)",
37
+ ),
38
+ base_url: Optional[str] = typer.Option(
39
+ None,
40
+ "--base-url",
41
+ "-u",
42
+ help="API base URL (default: https://api.trymaitai.ai/api/v1)",
43
+ ),
44
+ ):
45
+ """Authenticate and store your API key for future commands."""
46
+ key = api_key or get_api_key()
47
+ if not key:
48
+ console.print(
49
+ "[yellow]Provide your API key via --api-key, or set MAITAI_API_KEY.[/yellow]"
50
+ )
51
+ console.print(
52
+ "Create keys at [link=https://portal.trymaitai.com]portal.trymaitai.com[/link] → Settings → API Keys"
53
+ )
54
+ raise typer.Exit(1)
55
+ save_config(key, base_url)
56
+ console.print("[green]✓ Authenticated. API key saved to ~/.maitai/config[/green]")
57
+
58
+
59
+ @app.command()
60
+ def logout():
61
+ """Remove stored credentials."""
62
+ clear_config()
63
+ console.print("[green]✓ Logged out. Credentials removed.[/green]")
64
+
65
+
66
+ @app.command()
67
+ def docs():
68
+ """Show API documentation references."""
69
+ console.print("[bold]Maitai Developer API (api/v1)[/bold]")
70
+ console.print()
71
+ console.print(" [link=https://docs.trymaitai.ai]External docs[/link]")
72
+ console.print(
73
+ " [link=https://docs.trymaitai.ai/api-reference]API Reference[/link]"
74
+ )
75
+ console.print(
76
+ " [link=https://portal.trymaitai.com]Portal (create API keys)[/link]"
77
+ )
78
+ console.print()
79
+ console.print("[dim]In maitai-mono repo:[/dim]")
80
+ console.print(" docs/external/openapi.yaml - OpenAPI spec")
81
+ console.print(" docs/external/api-reference/ - API reference pages")
82
+ console.print(" docs/internal/ - Internal design docs")
83
+
84
+
85
+ @app.command()
86
+ def whoami():
87
+ """Show current authentication status."""
88
+ key = get_api_key()
89
+ base_url = get_base_url()
90
+ if not key:
91
+ console.print("[red]Not authenticated.[/red]")
92
+ console.print("Run [bold]maitai login[/bold] or set MAITAI_API_KEY.")
93
+ raise typer.Exit(1)
94
+ console.print("[green]Authenticated[/green]")
95
+ console.print(f" Base URL: {base_url}")
96
+ console.print(f" API key: {key[:8]}...{key[-4:]}")
97
+
98
+
99
+ # ─── Raw API (hand-written) ──────────────────────────────────────────────────
100
+
101
+
102
+ @app.command("api")
103
+ @handle_api_errors
104
+ def raw_api(
105
+ method: str = typer.Argument("GET", help="HTTP method"),
106
+ path: str = typer.Argument(..., help="API path (e.g. /applications)"),
107
+ body: Optional[str] = typer.Option(
108
+ None, "--body", "-b", help="JSON body for POST/PUT"
109
+ ),
110
+ ):
111
+ """Make a raw API request. Path is relative to base URL (e.g. /applications)."""
112
+ import json
113
+
114
+ client = get_client()
115
+ path = path if path.startswith("/") else f"/{path}"
116
+ json_body = json.loads(body) if body else None
117
+ if method.upper() == "GET":
118
+ r = client.get(path)
119
+ elif method.upper() == "POST":
120
+ r = client.post(path, json=json_body)
121
+ elif method.upper() == "PUT":
122
+ r = client.put(path, json=json_body)
123
+ elif method.upper() == "DELETE":
124
+ r = client.delete(path)
125
+ else:
126
+ console.print("[red]Unsupported method. Use GET, POST, PUT, or DELETE.[/red]")
127
+ raise typer.Exit(1)
128
+ print_json(r)
129
+
130
+
131
+ def main():
132
+ """Entry point."""
133
+ try:
134
+ app()
135
+ except KeyboardInterrupt:
136
+ raise typer.Exit(130)
137
+
138
+
139
+ if __name__ == "__main__":
140
+ main()
maitai_cli/utils.py ADDED
@@ -0,0 +1,46 @@
1
+ """Shared utilities for Maitai CLI commands."""
2
+
3
+ import functools
4
+ from typing import Any
5
+
6
+ import typer
7
+ from maitai_cli.api import APIClient, APIError
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+
13
+ def get_client() -> APIClient:
14
+ """Return an authenticated API client."""
15
+ return APIClient()
16
+
17
+
18
+ def print_json(data: Any, pagination: dict | None = None) -> None:
19
+ """Pretty-print JSON with optional pagination info."""
20
+ import json
21
+
22
+ from rich.syntax import Syntax
23
+
24
+ text = json.dumps(data, indent=2, default=str)
25
+ if pagination:
26
+ p = pagination
27
+ console.print(
28
+ f"[dim]total={p.get('total', '?')} offset={p.get('offset', 0)} limit={p.get('limit', 25)}[/dim]"
29
+ )
30
+ console.print(Syntax(text, "json", theme="monokai", line_numbers=False))
31
+
32
+
33
+ def handle_api_errors(f):
34
+ """Decorator to catch APIError and print a clean message."""
35
+
36
+ @functools.wraps(f)
37
+ def wrapper(*args, **kwargs):
38
+ try:
39
+ return f(*args, **kwargs)
40
+ except APIError as e:
41
+ console.print(f"[red]API Error ({e.status_code}): {e.message}[/red]")
42
+ if e.status_code == 401:
43
+ console.print("Run [bold]maitai login[/bold] or set MAITAI_API_KEY.")
44
+ raise typer.Exit(1)
45
+
46
+ return wrapper
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.4
2
+ Name: maitai-cli
3
+ Version: 0.1.1
4
+ Summary: CLI for the Maitai Platform Developer API (api/v1)
5
+ Author-email: Maitai <support@trymaitai.com>
6
+ License: MIT
7
+ Project-URL: Documentation, https://docs.trymaitai.ai
8
+ Project-URL: API Reference, https://docs.trymaitai.ai/api-reference
9
+ Project-URL: Portal, https://portal.trymaitai.com
10
+ Keywords: maitai,cli,api,ai,llm
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: typer>=0.9.0
23
+ Requires-Dist: httpx>=0.25.0
24
+ Requires-Dist: rich>=13.0.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest; extra == "dev"
27
+ Requires-Dist: ruff; extra == "dev"
28
+
29
+ # Maitai CLI
30
+
31
+ Command-line interface for the [Maitai Platform Developer API](https://docs.trymaitai.ai/api-reference) (api/v1).
32
+
33
+ Resource commands (`applications`, `agents`, etc.) are **generated from the OpenAPI spec**. When the API changes, run `python scripts/generate_openapi.py` from the repo root to regenerate the CLI.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ # From the maitai-mono repo (cli directory)
39
+ pip install .
40
+
41
+ # Or install in development mode
42
+ pip install -e .
43
+ ```
44
+
45
+ ## Authenticate
46
+
47
+ Create an API key in the [Portal](https://portal.trymaitai.com) under **Settings → API Keys**, then:
48
+
49
+ ```bash
50
+ # Interactive: saves key to ~/.maitai/config
51
+ maitai login --api-key YOUR_MAITAI_API_KEY
52
+
53
+ # Or use environment variable (no login needed)
54
+ export MAITAI_API_KEY="YOUR_MAITAI_API_KEY"
55
+ ```
56
+
57
+ For a custom base URL (e.g. local dev):
58
+
59
+ ```bash
60
+ maitai login --api-key YOUR_KEY --base-url http://localhost:5000/api/v1
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ ```bash
66
+ # Check auth status
67
+ maitai whoami
68
+
69
+ # List resources
70
+ maitai applications --list
71
+ maitai agents --list
72
+ maitai intent-groups --list
73
+ maitai sessions --list
74
+ maitai sentinels --list
75
+ maitai datasets --list
76
+ maitai test-sets --list
77
+ maitai test-runs --list
78
+ maitai models --list
79
+ maitai models --available
80
+ maitai evaluations --list
81
+ maitai finetune-runs --list
82
+ maitai conversation-trees --list
83
+
84
+ # Get by ID
85
+ maitai applications --get 42
86
+ maitai agents --get 1
87
+ maitai intent-groups --get 5
88
+
89
+ # Create application
90
+ maitai applications --create "My App:my-app"
91
+
92
+ # Delete application
93
+ maitai applications --delete 42
94
+
95
+ # Raw API calls (any endpoint)
96
+ maitai api GET /applications
97
+ maitai api GET /applications/42
98
+ maitai api POST /applications -b '{"application_name":"Test","application_ref_name":"test"}'
99
+ maitai api PUT /applications/42 -b '{"application_name":"Updated"}'
100
+ maitai api DELETE /applications/42
101
+ ```
102
+
103
+ ## API Reference
104
+
105
+ The CLI wraps the Maitai Developer API. Full documentation:
106
+
107
+ - **External docs**: [docs.trymaitai.ai](https://docs.trymaitai.ai)
108
+ - **OpenAPI spec**: `docs/external/openapi.yaml` in this repo
109
+ - **Internal docs**: `docs/internal/` in this repo
110
+
111
+ ### Authentication
112
+
113
+ All requests use the `X-Maitai-Api-Key` header. Create keys at [portal.trymaitai.com](https://portal.trymaitai.com) → Settings → API Keys.
114
+
115
+ ### Base URL
116
+
117
+ - Production: `https://api.trymaitai.ai/api/v1`
118
+ - Override with `MAITAI_BASE_URL` or `maitai login --base-url`
119
+
120
+ ### Response Format
121
+
122
+ - Success: `{"data": {...}}`
123
+ - Paginated: `{"data": [...], "pagination": {"total": N, "offset": 0, "limit": 25}}`
124
+ - Error: `{"error": {"status": 401, "message": "..."}}`
125
+
126
+ <!-- GENERATED_ENDPOINTS_START -->
127
+ ## Supported API endpoints
128
+
129
+ The CLI wraps all api/v1 endpoints. Use `maitai api METHOD path` for any endpoint:
130
+
131
+ | Method | Path | Operation |
132
+ |--------|------|-----------|
133
+ | GET | `/agents` | listAgents |
134
+ | POST | `/agents` | createAgent |
135
+ | DELETE | `/agents/actions/{action_id}` | deleteAgentAction |
136
+ | GET | `/agents/actions/{action_id}` | getAgentAction |
137
+ | PUT | `/agents/actions/{action_id}` | updateAgentAction |
138
+ | PUT | `/agents/actions/{action_id}/disable` | disableAction |
139
+ | PUT | `/agents/actions/{action_id}/enable` | enableAction |
140
+ | POST | `/agents/actions/{action_id}/publish` | publishActionVersion |
141
+ | GET | `/agents/actions/{action_id}/versions` | listActionVersions |
142
+ | GET | `/agents/tasks/{task_id}/tree` | getAgentTaskTree |
143
+ | DELETE | `/agents/{agent_id}` | deleteAgent |
144
+ | GET | `/agents/{agent_id}` | getAgent |
145
+ | PUT | `/agents/{agent_id}` | updateAgent |
146
+ | GET | `/agents/{agent_id}/actions` | listAgentActions |
147
+ | POST | `/agents/{agent_id}/actions` | createAgentAction |
148
+ | DELETE | `/agents/{agent_id}/form-fields` | deleteFormFields |
149
+ | GET | `/agents/{agent_id}/form-fields` | getFormFields |
150
+ | PUT | `/agents/{agent_id}/form-fields` | updateFormFields |
151
+ | POST | `/agents/{agent_id}/publish` | publishAgentVersion |
152
+ | GET | `/agents/{agent_id}/releases` | listAgentReleases |
153
+ | DELETE | `/agents/{agent_id}/releases/{name}` | deleteAgentRelease |
154
+ | PUT | `/agents/{agent_id}/releases/{name}` | upsertAgentRelease |
155
+ | GET | `/agents/{agent_id}/routing/config` | getRoutingConfig |
156
+ | PUT | `/agents/{agent_id}/routing/config` | updateRoutingConfig |
157
+ | POST | `/agents/{agent_id}/routing/rules` | createRoutingRule |
158
+ | DELETE | `/agents/{agent_id}/routing/rules/{rule_id}` | deleteRoutingRule |
159
+ | PUT | `/agents/{agent_id}/routing/rules/{rule_id}` | updateRoutingRule |
160
+ | GET | `/agents/{agent_id}/sessions` | listAgentSessions |
161
+ | GET | `/agents/{agent_id}/sessions/{session_id}` | getAgentSessionDetail |
162
+ | GET | `/agents/{agent_id}/sessions/{session_id}/timeline` | getAgentSessionTimeline |
163
+ | GET | `/agents/{agent_id}/subagents` | listSubAgents |
164
+ | POST | `/agents/{agent_id}/subagents` | addSubAgent |
165
+ | DELETE | `/agents/{agent_id}/subagents/{child_agent_id}` | removeSubAgent |
166
+ | PUT | `/agents/{agent_id}/subagents/{child_agent_id}/disable` | disableSubAgent |
167
+ | PUT | `/agents/{agent_id}/subagents/{child_agent_id}/enable` | enableSubAgent |
168
+ | GET | `/agents/{agent_id}/tasks/{task_id}/timeline` | getAgentTaskTimeline |
169
+ | GET | `/agents/{agent_id}/versions` | listAgentVersions |
170
+ | GET | `/agents/{agent_id}/versions/{version}` | getAgentVersion |
171
+ | GET | `/applications` | listApplications |
172
+ | POST | `/applications` | createApplication |
173
+ | DELETE | `/applications/{application_id}` | deleteApplication |
174
+ | GET | `/applications/{application_id}` | getApplication |
175
+ | PUT | `/applications/{application_id}` | updateApplication |
176
+ | GET | `/applications/{application_id}/config` | getApplicationConfig |
177
+ | PUT | `/applications/{application_id}/config` | updateApplicationConfig |
178
+ | GET | `/applications/{application_id}/intents` | listApplicationIntents |
179
+ | GET | `/applications/{application_id}/intents/{intent_id}` | getApplicationIntent |
180
+ | GET | `/applications/{application_id}/intents/{intent_id}/config` | getIntentConfig |
181
+ | PUT | `/applications/{application_id}/intents/{intent_id}/config` | updateIntentConfig |
182
+ | GET | `/applications/{application_id}/models` | listApplicationModels |
183
+ | GET | `/applications/{application_id}/sessions` | listApplicationSessions |
184
+ | GET | `/applications/{application_id}/workflow-runs` | listApplicationWorkflowRuns |
185
+ | GET | `/conversation-trees` | listConversationTrees |
186
+ | DELETE | `/conversation-trees/{tree_id}` | deleteConversationTree |
187
+ | GET | `/conversation-trees/{tree_id}` | getConversationTree |
188
+ | GET | `/conversation-trees/{tree_id}/status` | getConversationTreeStatus |
189
+ | GET | `/conversation-trees/{tree_id}/versions` | listConversationTreeVersions |
190
+ | GET | `/datasets` | listDatasets |
191
+ | POST | `/datasets` | createDataset |
192
+ | DELETE | `/datasets/{dataset_id}` | deleteDataset |
193
+ | GET | `/datasets/{dataset_id}` | getDataset |
194
+ | PUT | `/datasets/{dataset_id}` | updateDataset |
195
+ | DELETE | `/datasets/{dataset_id}/requests` | removeRequestsFromDataset |
196
+ | GET | `/datasets/{dataset_id}/requests` | listDatasetRequests |
197
+ | POST | `/datasets/{dataset_id}/requests` | addRequestsToDataset |
198
+ | GET | `/evaluations` | listEvaluations |
199
+ | POST | `/evaluations` | createEvaluation |
200
+ | GET | `/evaluations/{evaluation_run_id}` | getEvaluation |
201
+ | GET | `/evaluations/{evaluation_run_id}/results` | getEvaluationResults |
202
+ | GET | `/finetune-runs` | listFinetuneRuns |
203
+ | POST | `/finetune-runs` | createFinetuneRun |
204
+ | GET | `/finetune-runs/{run_id}` | getFinetuneRun |
205
+ | POST | `/finetune-runs/{run_id}/cancel` | cancelFinetuneRun |
206
+ | GET | `/finetune-runs/{run_id}/metrics` | getFinetuneRunMetrics |
207
+ | GET | `/intent-groups` | listIntentGroups |
208
+ | GET | `/intent-groups/{intent_group_id}` | getIntentGroup |
209
+ | GET | `/intent-groups/{intent_group_id}/compositions` | listIntentGroupCompositions |
210
+ | GET | `/intent-groups/{intent_group_id}/config` | getIntentGroupConfig |
211
+ | PUT | `/intent-groups/{intent_group_id}/config` | updateIntentGroupConfig |
212
+ | GET | `/intent-groups/{intent_group_id}/datasets` | listIntentGroupDatasets |
213
+ | GET | `/intent-groups/{intent_group_id}/intents` | listIntentsByGroup |
214
+ | GET | `/intent-groups/{intent_group_id}/models` | listIntentGroupModels |
215
+ | GET | `/intent-groups/{intent_group_id}/requests` | listIntentGroupRequests |
216
+ | GET | `/intent-groups/{intent_group_id}/sentinels` | listIntentGroupSentinels |
217
+ | GET | `/intent-groups/{intent_group_id}/test-sets` | listIntentGroupTestSets |
218
+ | GET | `/models` | listModels |
219
+ | GET | `/models/available` | listAvailableModels |
220
+ | POST | `/models/{model_id}/disable` | disableModel |
221
+ | POST | `/models/{model_id}/enable` | enableModel |
222
+ | GET | `/requests` | listRequests |
223
+ | GET | `/requests/{request_id}` | getRequest |
224
+ | PUT | `/requests/{request_id}/response` | updateRequestResponse |
225
+ | GET | `/sentinels` | listSentinels |
226
+ | POST | `/sentinels` | createSentinel |
227
+ | DELETE | `/sentinels/{sentinel_id}` | deleteSentinel |
228
+ | GET | `/sentinels/{sentinel_id}` | getSentinel |
229
+ | PUT | `/sentinels/{sentinel_id}` | updateSentinel |
230
+ | GET | `/sessions` | listSessions |
231
+ | GET | `/sessions/{session_id}` | getSession |
232
+ | POST | `/sessions/{session_id}/feedback` | setSessionFeedback |
233
+ | GET | `/sessions/{session_id}/timeline` | getSessionTimeline |
234
+ | GET | `/test-runs` | listTestRuns |
235
+ | POST | `/test-runs` | createTestRun |
236
+ | DELETE | `/test-runs/{test_run_id}` | deleteTestRun |
237
+ | GET | `/test-runs/{test_run_id}` | getTestRun |
238
+ | GET | `/test-runs/{test_run_id}/progress` | getTestRunProgress |
239
+ | GET | `/test-runs/{test_run_id}/requests` | listTestRunRequests |
240
+ | GET | `/test-sets` | listTestSets |
241
+ | POST | `/test-sets` | createTestSet |
242
+ | DELETE | `/test-sets/{test_set_id}` | deleteTestSet |
243
+ | GET | `/test-sets/{test_set_id}` | getTestSet |
244
+ | PUT | `/test-sets/{test_set_id}` | updateTestSet |
245
+ | DELETE | `/test-sets/{test_set_id}/requests` | removeRequestsFromTestSet |
246
+ | GET | `/test-sets/{test_set_id}/requests` | listTestSetRequests |
247
+ | POST | `/test-sets/{test_set_id}/requests` | addRequestsToTestSet |
248
+
249
+ <!-- GENERATED_ENDPOINTS_END -->
250
+
@@ -0,0 +1,12 @@
1
+ maitai_cli/__init__.py,sha256=DP17cdkmaB9BWDyNqe7FsDeMVkU2sjWLv5YKF3UgklE,104
2
+ maitai_cli/__main__.py,sha256=yTPCvg2BT1f-i59uxTzz64OXy7sU8GB2G5wz7fcau5c,118
3
+ maitai_cli/api.py,sha256=voFRukTGhauDocQyHQNr92q_UaqfZ0j7mv5Afr_x564,2953
4
+ maitai_cli/commands_generated.py,sha256=1Jgi13ijzbaqP_D2JEo-xLg0sXIEE02zfnK-9WncQgE,20838
5
+ maitai_cli/config.py,sha256=PcMIwfJkC5tMP9SKMhcbm5eGQWe6RzZx-mIjv2MKTxw,2383
6
+ maitai_cli/main.py,sha256=BuFB5F8XcDYqroW4H4YmnF17Ui4Mv3EolQrwRBJrEXs,4579
7
+ maitai_cli/utils.py,sha256=1gpMjzPwoJRSvMLFE2gz-2TEvYaa3anjirFNGtKuy_4,1289
8
+ maitai_cli-0.1.1.dist-info/METADATA,sha256=Sx9Ty1SBOezuEpSQ-7eXBqrdJsyAZ3AR_zoGhi4fspE,10951
9
+ maitai_cli-0.1.1.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
10
+ maitai_cli-0.1.1.dist-info/entry_points.txt,sha256=vxR-KCH2JQAnwJQDJ-i6uMD9O3nPFbQog7u66mY9OvU,47
11
+ maitai_cli-0.1.1.dist-info/top_level.txt,sha256=VDcocArAixYCOLTnI-KZl7AA_17CiuBvrieREKIig9w,11
12
+ maitai_cli-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ maitai = maitai_cli.main:app
@@ -0,0 +1 @@
1
+ maitai_cli