meshagent-cli 0.7.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.
- meshagent/cli/__init__.py +3 -0
- meshagent/cli/agent.py +263 -0
- meshagent/cli/api_keys.py +102 -0
- meshagent/cli/async_typer.py +31 -0
- meshagent/cli/auth.py +30 -0
- meshagent/cli/auth_async.py +295 -0
- meshagent/cli/call.py +224 -0
- meshagent/cli/chatbot.py +601 -0
- meshagent/cli/cli.py +186 -0
- meshagent/cli/cli_mcp.py +344 -0
- meshagent/cli/cli_secrets.py +414 -0
- meshagent/cli/common_options.py +32 -0
- meshagent/cli/containers.py +577 -0
- meshagent/cli/developer.py +70 -0
- meshagent/cli/exec.py +381 -0
- meshagent/cli/helper.py +147 -0
- meshagent/cli/helpers.py +131 -0
- meshagent/cli/mailbot.py +270 -0
- meshagent/cli/meeting_transcriber.py +124 -0
- meshagent/cli/messaging.py +160 -0
- meshagent/cli/oauth2.py +189 -0
- meshagent/cli/participant_token.py +61 -0
- meshagent/cli/projects.py +105 -0
- meshagent/cli/queue.py +91 -0
- meshagent/cli/room.py +214 -0
- meshagent/cli/services.py +490 -0
- meshagent/cli/sessions.py +26 -0
- meshagent/cli/storage.py +813 -0
- meshagent/cli/version.py +1 -0
- meshagent/cli/voicebot.py +178 -0
- meshagent/cli/webhook.py +100 -0
- meshagent_cli-0.7.0.dist-info/METADATA +47 -0
- meshagent_cli-0.7.0.dist-info/RECORD +36 -0
- meshagent_cli-0.7.0.dist-info/WHEEL +5 -0
- meshagent_cli-0.7.0.dist-info/entry_points.txt +2 -0
- meshagent_cli-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# ---------------------------------------------------------------------------
|
|
2
|
+
# Imports
|
|
3
|
+
# ---------------------------------------------------------------------------
|
|
4
|
+
import typer
|
|
5
|
+
from rich import print
|
|
6
|
+
from typing import Annotated, Optional
|
|
7
|
+
from meshagent.cli.common_options import ProjectIdOption
|
|
8
|
+
from aiohttp import ClientResponseError
|
|
9
|
+
import pathlib
|
|
10
|
+
from meshagent.cli import async_typer
|
|
11
|
+
from meshagent.api.services import well_known_service_path
|
|
12
|
+
from meshagent.api.specs.service import ServiceSpec
|
|
13
|
+
from meshagent.api.keys import parse_api_key
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import shlex
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import signal
|
|
20
|
+
import atexit
|
|
21
|
+
import ctypes
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
from meshagent.cli.helper import (
|
|
26
|
+
get_client,
|
|
27
|
+
print_json_table,
|
|
28
|
+
resolve_project_id,
|
|
29
|
+
resolve_room,
|
|
30
|
+
resolve_key,
|
|
31
|
+
)
|
|
32
|
+
from meshagent.api import (
|
|
33
|
+
ParticipantToken,
|
|
34
|
+
ApiScope,
|
|
35
|
+
)
|
|
36
|
+
from meshagent.cli.common_options import OutputFormatOption
|
|
37
|
+
|
|
38
|
+
from pydantic_yaml import parse_yaml_raw_as
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
from meshagent.cli.call import _make_call
|
|
42
|
+
|
|
43
|
+
app = async_typer.AsyncTyper(help="Manage services for your project")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@app.async_command("create")
|
|
47
|
+
async def service_create(
|
|
48
|
+
*,
|
|
49
|
+
project_id: ProjectIdOption = None,
|
|
50
|
+
file: Annotated[
|
|
51
|
+
str,
|
|
52
|
+
typer.Option("--file", "-f", help="File path to a service definition"),
|
|
53
|
+
],
|
|
54
|
+
room: Annotated[
|
|
55
|
+
Optional[str],
|
|
56
|
+
typer.Option(
|
|
57
|
+
"--room", "-r", help="The name of a room to create the service for"
|
|
58
|
+
),
|
|
59
|
+
] = None,
|
|
60
|
+
):
|
|
61
|
+
"""Create a service attached to the project."""
|
|
62
|
+
client = await get_client()
|
|
63
|
+
try:
|
|
64
|
+
project_id = await resolve_project_id(project_id)
|
|
65
|
+
|
|
66
|
+
with open(str(pathlib.Path(file).expanduser().resolve()), "rb") as f:
|
|
67
|
+
spec = parse_yaml_raw_as(ServiceSpec, f.read())
|
|
68
|
+
|
|
69
|
+
if spec.id is not None:
|
|
70
|
+
print("[red]id cannot be set when creating a service[/red]")
|
|
71
|
+
raise typer.Exit(code=1)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
if room is None:
|
|
75
|
+
new_id = await client.create_service(
|
|
76
|
+
project_id=project_id, service=spec
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
new_id = await client.create_room_service(
|
|
80
|
+
project_id=project_id, service=spec, room_name=room
|
|
81
|
+
)
|
|
82
|
+
except ClientResponseError as exc:
|
|
83
|
+
if exc.status == 409:
|
|
84
|
+
print(f"[red]Service name already in use: {spec.metadata.name}[/red]")
|
|
85
|
+
raise typer.Exit(code=1)
|
|
86
|
+
raise
|
|
87
|
+
else:
|
|
88
|
+
print(f"[green]Created service:[/] {new_id}")
|
|
89
|
+
|
|
90
|
+
finally:
|
|
91
|
+
await client.close()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.async_command("update")
|
|
95
|
+
async def service_update(
|
|
96
|
+
*,
|
|
97
|
+
project_id: ProjectIdOption = None,
|
|
98
|
+
id: Optional[str] = None,
|
|
99
|
+
file: Annotated[
|
|
100
|
+
str,
|
|
101
|
+
typer.Option("--file", "-f", help="File path to a service definition"),
|
|
102
|
+
],
|
|
103
|
+
create: Annotated[
|
|
104
|
+
Optional[bool],
|
|
105
|
+
typer.Option(
|
|
106
|
+
help="create the service if it does not exist",
|
|
107
|
+
),
|
|
108
|
+
] = False,
|
|
109
|
+
room: Annotated[
|
|
110
|
+
Optional[str],
|
|
111
|
+
typer.Option(
|
|
112
|
+
"--room", "-r", help="The name of a room to update the service for"
|
|
113
|
+
),
|
|
114
|
+
] = None,
|
|
115
|
+
):
|
|
116
|
+
"""Create a service attached to the project."""
|
|
117
|
+
client = await get_client()
|
|
118
|
+
try:
|
|
119
|
+
project_id = await resolve_project_id(project_id)
|
|
120
|
+
|
|
121
|
+
with open(str(pathlib.Path(file).expanduser().resolve()), "rb") as f:
|
|
122
|
+
spec = parse_yaml_raw_as(ServiceSpec, f.read())
|
|
123
|
+
if spec.id is not None:
|
|
124
|
+
id = spec.id
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
if id is None:
|
|
128
|
+
if room is None:
|
|
129
|
+
services = await client.list_services(project_id=project_id)
|
|
130
|
+
else:
|
|
131
|
+
services = await client.list_room_services(
|
|
132
|
+
project_id=project_id, room_name=room
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
for s in services:
|
|
136
|
+
if s.metadata.name == spec.metadata.name:
|
|
137
|
+
id = s.id
|
|
138
|
+
|
|
139
|
+
if id is None and not create:
|
|
140
|
+
print("[red]pass a service id or specify --create[/red]")
|
|
141
|
+
raise typer.Exit(code=1)
|
|
142
|
+
|
|
143
|
+
if id is None:
|
|
144
|
+
if room is None:
|
|
145
|
+
id = await client.create_service(
|
|
146
|
+
project_id=project_id, service=spec
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
id = await client.create_room_service(
|
|
150
|
+
project_id=project_id, service=spec, room_name=room
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
else:
|
|
154
|
+
spec.id = id
|
|
155
|
+
if room is None:
|
|
156
|
+
await client.update_service(
|
|
157
|
+
project_id=project_id, service_id=id, service=spec
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
await client.update_room_service(
|
|
161
|
+
project_id=project_id,
|
|
162
|
+
service_id=id,
|
|
163
|
+
service=spec,
|
|
164
|
+
room_name=room,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
except ClientResponseError as exc:
|
|
168
|
+
if exc.status == 409:
|
|
169
|
+
print(f"[red]Service name already in use: {spec.metadata.name}[/red]")
|
|
170
|
+
raise typer.Exit(code=1)
|
|
171
|
+
raise
|
|
172
|
+
else:
|
|
173
|
+
print(f"[green]Updated service:[/] {id}")
|
|
174
|
+
|
|
175
|
+
finally:
|
|
176
|
+
await client.close()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@app.async_command("run")
|
|
180
|
+
async def service_run(
|
|
181
|
+
*,
|
|
182
|
+
project_id: ProjectIdOption = None,
|
|
183
|
+
command: str,
|
|
184
|
+
port: Annotated[
|
|
185
|
+
int,
|
|
186
|
+
typer.Option(
|
|
187
|
+
"--port",
|
|
188
|
+
"-p",
|
|
189
|
+
help=(
|
|
190
|
+
"a port number to run the agent on (will set MESHAGENT_PORT environment variable when launching the service)"
|
|
191
|
+
),
|
|
192
|
+
),
|
|
193
|
+
] = None,
|
|
194
|
+
room: Annotated[
|
|
195
|
+
Optional[str],
|
|
196
|
+
typer.Option(
|
|
197
|
+
help="A room name to test the service in (must not be currently running)"
|
|
198
|
+
),
|
|
199
|
+
] = None,
|
|
200
|
+
key: Annotated[
|
|
201
|
+
str,
|
|
202
|
+
typer.Option("--key", help="an api key to sign the token with"),
|
|
203
|
+
] = None,
|
|
204
|
+
):
|
|
205
|
+
key = await resolve_key(project_id=project_id, key=key)
|
|
206
|
+
|
|
207
|
+
if port is None:
|
|
208
|
+
import socket
|
|
209
|
+
|
|
210
|
+
def find_free_port():
|
|
211
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
212
|
+
s.bind(("", 0)) # Bind to a free port provided by the host.
|
|
213
|
+
s.listen(1)
|
|
214
|
+
return s.getsockname()[1]
|
|
215
|
+
|
|
216
|
+
port = find_free_port()
|
|
217
|
+
|
|
218
|
+
my_client = await get_client()
|
|
219
|
+
try:
|
|
220
|
+
project_id = await resolve_project_id(project_id)
|
|
221
|
+
room = resolve_room(room)
|
|
222
|
+
|
|
223
|
+
if room is None:
|
|
224
|
+
print("[bold red]Room was not set[/bold red]")
|
|
225
|
+
raise typer.Exit(1)
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
parsed_key = parse_api_key(key)
|
|
229
|
+
token = ParticipantToken(
|
|
230
|
+
name="cli", project_id=project_id, api_key_id=parsed_key.id
|
|
231
|
+
)
|
|
232
|
+
token.add_api_grant(ApiScope.agent_default())
|
|
233
|
+
token.add_role_grant("user")
|
|
234
|
+
token.add_room_grant(room)
|
|
235
|
+
|
|
236
|
+
print("[bold green]Connecting to room...[/bold green]")
|
|
237
|
+
|
|
238
|
+
run_tasks = []
|
|
239
|
+
|
|
240
|
+
async def run_service(port: int):
|
|
241
|
+
code, output = await _run_process(
|
|
242
|
+
cmd=shlex.split("python3 " + command),
|
|
243
|
+
log=True,
|
|
244
|
+
env={**os.environ, "MESHAGENT_PORT": str(port)},
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if code != 0:
|
|
248
|
+
print(f"[red]{output}[/red]")
|
|
249
|
+
|
|
250
|
+
run_tasks.append(asyncio.create_task(run_service(port)))
|
|
251
|
+
|
|
252
|
+
async def get_spec(port: int, attempt=0) -> ServiceSpec:
|
|
253
|
+
import aiohttp
|
|
254
|
+
|
|
255
|
+
max_attempts = 10
|
|
256
|
+
|
|
257
|
+
url = f"http://localhost:{port}{well_known_service_path}"
|
|
258
|
+
|
|
259
|
+
async with aiohttp.ClientSession() as session:
|
|
260
|
+
try:
|
|
261
|
+
res = await session.get(url=url)
|
|
262
|
+
res.raise_for_status()
|
|
263
|
+
|
|
264
|
+
spec_json = await res.json()
|
|
265
|
+
|
|
266
|
+
return ServiceSpec.model_validate(spec_json)
|
|
267
|
+
|
|
268
|
+
except Exception:
|
|
269
|
+
if attempt < max_attempts:
|
|
270
|
+
backoff = 0.1 * pow(2, attempt)
|
|
271
|
+
await asyncio.sleep(backoff)
|
|
272
|
+
return await get_spec(port, attempt + 1)
|
|
273
|
+
else:
|
|
274
|
+
print("[red]unable to read service spec[/red]")
|
|
275
|
+
raise typer.Exit(-1)
|
|
276
|
+
|
|
277
|
+
spec = await get_spec(port)
|
|
278
|
+
|
|
279
|
+
sys.stdout.write("\n")
|
|
280
|
+
|
|
281
|
+
for p in spec.ports:
|
|
282
|
+
print(f"[bold green]Connecting port {p.num}...[/bold green]")
|
|
283
|
+
|
|
284
|
+
for endpoint in p.endpoints:
|
|
285
|
+
print(
|
|
286
|
+
f"[bold green]Connecting endpoint {endpoint.path}...[/bold green]"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
run_tasks.append(
|
|
290
|
+
asyncio.create_task(
|
|
291
|
+
_make_call(
|
|
292
|
+
room=room,
|
|
293
|
+
project_id=project_id,
|
|
294
|
+
participant_name=endpoint.meshagent.identity,
|
|
295
|
+
url=f"http://localhost:{p.num}{endpoint.path}",
|
|
296
|
+
arguments={},
|
|
297
|
+
key=key,
|
|
298
|
+
permissions=endpoint.meshagent.api,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
await asyncio.gather(*run_tasks)
|
|
304
|
+
|
|
305
|
+
except ClientResponseError as exc:
|
|
306
|
+
if exc.status == 409:
|
|
307
|
+
print(f"[red]Room already in use: {room}[/red]")
|
|
308
|
+
raise typer.Exit(code=1)
|
|
309
|
+
raise
|
|
310
|
+
|
|
311
|
+
except Exception as e:
|
|
312
|
+
print(f"[red]{e}[/red]")
|
|
313
|
+
raise typer.Exit(code=1)
|
|
314
|
+
|
|
315
|
+
finally:
|
|
316
|
+
await my_client.close()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@app.async_command("show")
|
|
320
|
+
async def service_show(
|
|
321
|
+
*,
|
|
322
|
+
project_id: ProjectIdOption = None,
|
|
323
|
+
service_id: Annotated[str, typer.Argument(help="ID of the service to show")],
|
|
324
|
+
):
|
|
325
|
+
"""Show a services for the project."""
|
|
326
|
+
client = await get_client()
|
|
327
|
+
try:
|
|
328
|
+
project_id = await resolve_project_id(project_id)
|
|
329
|
+
service = await client.get_service(
|
|
330
|
+
project_id=project_id, service_id=service_id
|
|
331
|
+
) # → List[Service]
|
|
332
|
+
print(service.model_dump(mode="json"))
|
|
333
|
+
finally:
|
|
334
|
+
await client.close()
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@app.async_command("list")
|
|
338
|
+
async def service_list(
|
|
339
|
+
*,
|
|
340
|
+
project_id: ProjectIdOption = None,
|
|
341
|
+
o: OutputFormatOption = "table",
|
|
342
|
+
room: Annotated[
|
|
343
|
+
Optional[str],
|
|
344
|
+
typer.Option(
|
|
345
|
+
"--room", "-r", help="The name of a room to list the services for"
|
|
346
|
+
),
|
|
347
|
+
] = None,
|
|
348
|
+
):
|
|
349
|
+
"""List all services for the project."""
|
|
350
|
+
client = await get_client()
|
|
351
|
+
try:
|
|
352
|
+
project_id = await resolve_project_id(project_id)
|
|
353
|
+
services: list[ServiceSpec] = (
|
|
354
|
+
(await client.list_services(project_id=project_id))
|
|
355
|
+
if room is None
|
|
356
|
+
else (
|
|
357
|
+
await client.list_room_services(project_id=project_id, room_name=room)
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
if o == "json":
|
|
362
|
+
print(
|
|
363
|
+
{"services": [svc.model_dump(mode="json") for svc in services]}
|
|
364
|
+
).model_dump_json(indent=2)
|
|
365
|
+
else:
|
|
366
|
+
print_json_table(
|
|
367
|
+
[
|
|
368
|
+
{
|
|
369
|
+
"id": svc.id,
|
|
370
|
+
"name": svc.metadata.name,
|
|
371
|
+
"image": svc.container.image
|
|
372
|
+
if svc.container is not None
|
|
373
|
+
else None,
|
|
374
|
+
}
|
|
375
|
+
for svc in services
|
|
376
|
+
],
|
|
377
|
+
"id",
|
|
378
|
+
"name",
|
|
379
|
+
"image",
|
|
380
|
+
)
|
|
381
|
+
finally:
|
|
382
|
+
await client.close()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@app.async_command("delete")
|
|
386
|
+
async def service_delete(
|
|
387
|
+
*,
|
|
388
|
+
project_id: ProjectIdOption = None,
|
|
389
|
+
service_id: Annotated[str, typer.Argument(help="ID of the service to delete")],
|
|
390
|
+
room: Annotated[
|
|
391
|
+
Optional[str],
|
|
392
|
+
typer.Option(
|
|
393
|
+
"--room", "-r", help="The name of a room to delete the service for"
|
|
394
|
+
),
|
|
395
|
+
] = None,
|
|
396
|
+
):
|
|
397
|
+
"""Delete a service."""
|
|
398
|
+
client = await get_client()
|
|
399
|
+
try:
|
|
400
|
+
project_id = await resolve_project_id(project_id)
|
|
401
|
+
if room is None:
|
|
402
|
+
await client.delete_service(project_id=project_id, service_id=service_id)
|
|
403
|
+
else:
|
|
404
|
+
await client.delete_service(
|
|
405
|
+
project_id=project_id, service_id=service_id, room_name=room
|
|
406
|
+
)
|
|
407
|
+
print(f"[green]Service {service_id} deleted.[/]")
|
|
408
|
+
finally:
|
|
409
|
+
await client.close()
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
async def _run_process(
|
|
413
|
+
cmd: list[str], cwd=None, env=None, timeout: float | None = None, log: bool = False
|
|
414
|
+
) -> tuple[int, str]:
|
|
415
|
+
"""
|
|
416
|
+
Spawn a process, stream its output line-by-line as it runs, and return its exit code.
|
|
417
|
+
stdout+stderr are merged to preserve ordering.
|
|
418
|
+
"""
|
|
419
|
+
proc = await asyncio.create_subprocess_exec(
|
|
420
|
+
*cmd,
|
|
421
|
+
cwd=cwd,
|
|
422
|
+
env=env,
|
|
423
|
+
stdout=asyncio.subprocess.PIPE,
|
|
424
|
+
stderr=asyncio.subprocess.STDOUT,
|
|
425
|
+
preexec_fn=_preexec_fn,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
_spawned.append(proc)
|
|
429
|
+
|
|
430
|
+
output = []
|
|
431
|
+
try:
|
|
432
|
+
# Stream lines as they appear
|
|
433
|
+
assert proc.stdout is not None
|
|
434
|
+
while True:
|
|
435
|
+
line = (
|
|
436
|
+
await asyncio.wait_for(proc.stdout.readline(), timeout=timeout)
|
|
437
|
+
if timeout
|
|
438
|
+
else await proc.stdout.readline()
|
|
439
|
+
)
|
|
440
|
+
if not line:
|
|
441
|
+
break
|
|
442
|
+
ln = line.decode(errors="replace").rstrip()
|
|
443
|
+
if log:
|
|
444
|
+
print(ln, flush=True)
|
|
445
|
+
output.append(ln) # or send to a logger/queue
|
|
446
|
+
|
|
447
|
+
return await proc.wait(), "".join(output)
|
|
448
|
+
except asyncio.TimeoutError:
|
|
449
|
+
# Graceful shutdown on timeout
|
|
450
|
+
proc.terminate()
|
|
451
|
+
try:
|
|
452
|
+
await asyncio.wait_for(proc.wait(), 5)
|
|
453
|
+
except asyncio.TimeoutError:
|
|
454
|
+
proc.kill()
|
|
455
|
+
await proc.wait()
|
|
456
|
+
raise
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
# Linux-only: send SIGTERM to child if parent dies
|
|
460
|
+
_PRCTL_AVAILABLE = sys.platform.startswith("linux")
|
|
461
|
+
if _PRCTL_AVAILABLE:
|
|
462
|
+
libc = ctypes.CDLL("libc.so.6", use_errno=True)
|
|
463
|
+
PR_SET_PDEATHSIG = 1
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _preexec_fn():
|
|
467
|
+
# Make child the leader of a new session/process group
|
|
468
|
+
os.setsid()
|
|
469
|
+
# On Linux, ensure child gets SIGTERM if parent dies unexpectedly
|
|
470
|
+
if _PRCTL_AVAILABLE:
|
|
471
|
+
if libc.prctl(PR_SET_PDEATHSIG, signal.SIGTERM) != 0:
|
|
472
|
+
err = ctypes.get_errno()
|
|
473
|
+
raise OSError(err, "prctl(PR_SET_PDEATHSIG) failed")
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
_spawned = []
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _cleanup():
|
|
480
|
+
# Kill each child's process group (created by setsid)
|
|
481
|
+
for p in _spawned:
|
|
482
|
+
try:
|
|
483
|
+
os.killpg(p.pid, signal.SIGTERM)
|
|
484
|
+
except ProcessLookupError:
|
|
485
|
+
pass
|
|
486
|
+
except Exception:
|
|
487
|
+
pass
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
atexit.register(_cleanup)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from meshagent.cli import async_typer
|
|
2
|
+
from meshagent.cli.helper import get_client, print_json_table, resolve_project_id
|
|
3
|
+
from meshagent.cli.common_options import ProjectIdOption
|
|
4
|
+
|
|
5
|
+
app = async_typer.AsyncTyper()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@app.async_command("list")
|
|
9
|
+
async def list(*, project_id: ProjectIdOption = None):
|
|
10
|
+
client = await get_client()
|
|
11
|
+
sessions = await client.list_recent_sessions(
|
|
12
|
+
project_id=await resolve_project_id(project_id=project_id)
|
|
13
|
+
)
|
|
14
|
+
print_json_table(sessions["sessions"])
|
|
15
|
+
await client.close()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.async_command("show")
|
|
19
|
+
async def show(*, project_id: ProjectIdOption = None, session_id: str):
|
|
20
|
+
client = await get_client()
|
|
21
|
+
events = await client.list_session_events(
|
|
22
|
+
project_id=await resolve_project_id(project_id=project_id),
|
|
23
|
+
session_id=session_id,
|
|
24
|
+
)
|
|
25
|
+
print_json_table(events["events"], "type", "data")
|
|
26
|
+
await client.close()
|