meshagent-cli 0.7.0__py3-none-any.whl → 0.23.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 (42) hide show
  1. meshagent/cli/agent.py +23 -13
  2. meshagent/cli/api_keys.py +4 -4
  3. meshagent/cli/async_typer.py +52 -4
  4. meshagent/cli/call.py +27 -36
  5. meshagent/cli/chatbot.py +1559 -177
  6. meshagent/cli/cli.py +23 -22
  7. meshagent/cli/cli_mcp.py +92 -28
  8. meshagent/cli/cli_secrets.py +10 -10
  9. meshagent/cli/common_options.py +19 -4
  10. meshagent/cli/containers.py +164 -16
  11. meshagent/cli/database.py +997 -0
  12. meshagent/cli/developer.py +3 -3
  13. meshagent/cli/exec.py +22 -6
  14. meshagent/cli/helper.py +101 -12
  15. meshagent/cli/helpers.py +65 -11
  16. meshagent/cli/host.py +41 -0
  17. meshagent/cli/mailbot.py +1104 -79
  18. meshagent/cli/mailboxes.py +223 -0
  19. meshagent/cli/meeting_transcriber.py +29 -15
  20. meshagent/cli/messaging.py +7 -10
  21. meshagent/cli/multi.py +357 -0
  22. meshagent/cli/oauth2.py +192 -40
  23. meshagent/cli/participant_token.py +5 -3
  24. meshagent/cli/port.py +70 -0
  25. meshagent/cli/queue.py +2 -2
  26. meshagent/cli/room.py +24 -212
  27. meshagent/cli/rooms.py +214 -0
  28. meshagent/cli/services.py +269 -37
  29. meshagent/cli/sessions.py +5 -5
  30. meshagent/cli/storage.py +5 -5
  31. meshagent/cli/sync.py +434 -0
  32. meshagent/cli/task_runner.py +1317 -0
  33. meshagent/cli/version.py +1 -1
  34. meshagent/cli/voicebot.py +544 -98
  35. meshagent/cli/webhook.py +7 -7
  36. meshagent/cli/worker.py +1403 -0
  37. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/METADATA +15 -13
  38. meshagent_cli-0.23.0.dist-info/RECORD +45 -0
  39. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/WHEEL +1 -1
  40. meshagent_cli-0.7.0.dist-info/RECORD +0 -36
  41. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/entry_points.txt +0 -0
  42. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/top_level.txt +0 -0
meshagent/cli/services.py CHANGED
@@ -9,7 +9,7 @@ from aiohttp import ClientResponseError
9
9
  import pathlib
10
10
  from meshagent.cli import async_typer
11
11
  from meshagent.api.services import well_known_service_path
12
- from meshagent.api.specs.service import ServiceSpec
12
+ from meshagent.api.specs.service import ServiceSpec, ServiceTemplateSpec
13
13
  from meshagent.api.keys import parse_api_key
14
14
 
15
15
  import asyncio
@@ -35,27 +35,53 @@ from meshagent.api import (
35
35
  )
36
36
  from meshagent.cli.common_options import OutputFormatOption
37
37
 
38
+ from pydantic import RootModel
38
39
  from pydantic_yaml import parse_yaml_raw_as
39
40
 
40
41
 
41
42
  from meshagent.cli.call import _make_call
42
43
 
44
+
43
45
  app = async_typer.AsyncTyper(help="Manage services for your project")
44
46
 
45
47
 
48
+ class ServiceTemplateValues(RootModel[dict[str, str]]):
49
+ pass
50
+
51
+
52
+ def _load_template_values(
53
+ values_file: Optional[str],
54
+ values: Optional[list[str]] = None,
55
+ ) -> dict[str, str]:
56
+ template_values: dict[str, str] = {}
57
+
58
+ if values_file is not None:
59
+ with open(str(pathlib.Path(values_file).expanduser().resolve()), "rb") as f:
60
+ template_values = parse_yaml_raw_as(ServiceTemplateValues, f.read()).root
61
+
62
+ if values:
63
+ for item in values:
64
+ if "=" not in item:
65
+ raise typer.BadParameter("Template values must be key=value")
66
+ key, value = item.split("=", 1)
67
+ if not key:
68
+ raise typer.BadParameter("Template values must include a key")
69
+ template_values[key] = value
70
+
71
+ return template_values
72
+
73
+
46
74
  @app.async_command("create")
47
75
  async def service_create(
48
76
  *,
49
- project_id: ProjectIdOption = None,
77
+ project_id: ProjectIdOption,
50
78
  file: Annotated[
51
79
  str,
52
80
  typer.Option("--file", "-f", help="File path to a service definition"),
53
81
  ],
54
82
  room: Annotated[
55
83
  Optional[str],
56
- typer.Option(
57
- "--room", "-r", help="The name of a room to create the service for"
58
- ),
84
+ typer.Option("--room", help="The name of a room to create the service for"),
59
85
  ] = None,
60
86
  ):
61
87
  """Create a service attached to the project."""
@@ -94,7 +120,7 @@ async def service_create(
94
120
  @app.async_command("update")
95
121
  async def service_update(
96
122
  *,
97
- project_id: ProjectIdOption = None,
123
+ project_id: ProjectIdOption,
98
124
  id: Optional[str] = None,
99
125
  file: Annotated[
100
126
  str,
@@ -108,9 +134,7 @@ async def service_update(
108
134
  ] = False,
109
135
  room: Annotated[
110
136
  Optional[str],
111
- typer.Option(
112
- "--room", "-r", help="The name of a room to update the service for"
113
- ),
137
+ typer.Option("--room", help="The name of a room to update the service for"),
114
138
  ] = None,
115
139
  ):
116
140
  """Create a service attached to the project."""
@@ -176,10 +200,217 @@ async def service_update(
176
200
  await client.close()
177
201
 
178
202
 
203
+ @app.async_command("validate")
204
+ async def service_validate(
205
+ *,
206
+ file: Annotated[
207
+ str,
208
+ typer.Option("--file", "-f", help="File path to a service definition"),
209
+ ],
210
+ ):
211
+ """Validate a service spec from a YAML file."""
212
+ try:
213
+ with open(str(pathlib.Path(file).expanduser().resolve()), "rb") as f:
214
+ spec = parse_yaml_raw_as(ServiceSpec, f.read())
215
+ except Exception as exc:
216
+ print(f"[red]Invalid service spec: {exc}[/red]")
217
+ raise typer.Exit(code=1)
218
+
219
+ print(f"[green]Service spec is valid:[/] {spec.metadata.name}")
220
+
221
+
222
+ @app.async_command("create-template")
223
+ async def service_create_template(
224
+ *,
225
+ project_id: ProjectIdOption,
226
+ file: Annotated[
227
+ str,
228
+ typer.Option("--file", "-f", help="File path to a service template"),
229
+ ],
230
+ values: Annotated[
231
+ Optional[str],
232
+ typer.Option("--values-file", help="File path to template values"),
233
+ ] = None,
234
+ value: Annotated[
235
+ Optional[list[str]],
236
+ typer.Option(
237
+ "--value",
238
+ "-v",
239
+ help="Template value override (key=value)",
240
+ ),
241
+ ] = None,
242
+ room: Annotated[
243
+ Optional[str],
244
+ typer.Option("--room", help="The name of a room to create the service for"),
245
+ ] = None,
246
+ ):
247
+ """Create a service from a ServiceTemplate spec."""
248
+ client = await get_client()
249
+ try:
250
+ project_id = await resolve_project_id(project_id)
251
+
252
+ with open(str(pathlib.Path(file).expanduser().resolve()), "rb") as f:
253
+ template = f.read()
254
+
255
+ template_values = _load_template_values(values, value)
256
+
257
+ try:
258
+ if room is None:
259
+ service = await client.create_service_from_template(
260
+ project_id=project_id, template=template, values=template_values
261
+ )
262
+ else:
263
+ service = await client.create_room_service_from_template(
264
+ project_id=project_id,
265
+ template=template,
266
+ values=template_values,
267
+ room_name=room,
268
+ )
269
+ except ClientResponseError as exc:
270
+ if exc.status == 409:
271
+ print(
272
+ f"[red]Service name already in use: {template.metadata.name}[/red]"
273
+ )
274
+ raise typer.Exit(code=1)
275
+ raise
276
+ else:
277
+ service_id = service.id or ""
278
+ print(f"[green]Created service:[/] {service_id}")
279
+
280
+ finally:
281
+ await client.close()
282
+
283
+
284
+ @app.async_command("update-template")
285
+ async def service_update_template(
286
+ *,
287
+ project_id: ProjectIdOption,
288
+ id: Optional[str] = None,
289
+ file: Annotated[
290
+ str,
291
+ typer.Option("--file", "-f", help="File path to a service template"),
292
+ ],
293
+ values: Annotated[
294
+ Optional[str],
295
+ typer.Option("--values-file", help="File path to template values"),
296
+ ] = None,
297
+ value: Annotated[
298
+ Optional[list[str]],
299
+ typer.Option(
300
+ "--value",
301
+ "-v",
302
+ help="Template value override (key=value)",
303
+ ),
304
+ ] = None,
305
+ create: Annotated[
306
+ Optional[bool],
307
+ typer.Option(
308
+ help="create the service if it does not exist",
309
+ ),
310
+ ] = False,
311
+ room: Annotated[
312
+ Optional[str],
313
+ typer.Option("--room", help="The name of a room to update the service for"),
314
+ ] = None,
315
+ ):
316
+ """Update a service using a ServiceTemplate spec."""
317
+ client = await get_client()
318
+ try:
319
+ project_id = await resolve_project_id(project_id)
320
+
321
+ with open(str(pathlib.Path(file).expanduser().resolve()), "rb") as f:
322
+ template = f.read()
323
+
324
+ template_values = _load_template_values(values, value)
325
+
326
+ try:
327
+ if id is None:
328
+ if room is None:
329
+ services = await client.list_services(project_id=project_id)
330
+ else:
331
+ services = await client.list_room_services(
332
+ project_id=project_id, room_name=room
333
+ )
334
+
335
+ for s in services:
336
+ if s.metadata.name == template.metadata.name:
337
+ id = s.id
338
+
339
+ if id is None and not create:
340
+ print("[red]pass a service id or specify --create[/red]")
341
+ raise typer.Exit(code=1)
342
+
343
+ if id is None:
344
+ if room is None:
345
+ service = await client.create_service_from_template(
346
+ project_id=project_id,
347
+ template=template,
348
+ values=template_values,
349
+ )
350
+ else:
351
+ service = await client.create_room_service_from_template(
352
+ project_id=project_id,
353
+ template=template,
354
+ values=template_values,
355
+ room_name=room,
356
+ )
357
+ id = service.id
358
+ else:
359
+ if room is None:
360
+ service = await client.update_service_from_template(
361
+ project_id=project_id,
362
+ service_id=id,
363
+ template=template,
364
+ values=template_values,
365
+ )
366
+ else:
367
+ service = await client.update_room_service_from_template(
368
+ project_id=project_id,
369
+ service_id=id,
370
+ template=template,
371
+ values=template_values,
372
+ room_name=room,
373
+ )
374
+ if service.id is not None:
375
+ id = service.id
376
+
377
+ except ClientResponseError as exc:
378
+ if exc.status == 409:
379
+ print(
380
+ f"[red]Service name already in use: {template.metadata.name}[/red]"
381
+ )
382
+ raise typer.Exit(code=1)
383
+ raise
384
+ else:
385
+ print(f"[green]Updated service:[/] {id}")
386
+
387
+ finally:
388
+ await client.close()
389
+
390
+
391
+ @app.async_command("validate-template")
392
+ async def service_validate_template(
393
+ *,
394
+ file: Annotated[
395
+ str,
396
+ typer.Option("--file", "-f", help="File path to a service template"),
397
+ ],
398
+ ):
399
+ """Validate a service template from a YAML file."""
400
+ try:
401
+ with open(str(pathlib.Path(file).expanduser().resolve()), "rb") as f:
402
+ template = parse_yaml_raw_as(ServiceTemplateSpec, f.read())
403
+ except Exception as exc:
404
+ print(f"[red]Invalid service template: {exc}[/red]")
405
+ raise typer.Exit(code=1)
406
+
407
+ print(f"[green]Service template is valid:[/] {template.metadata.name}")
408
+
409
+
179
410
  @app.async_command("run")
180
411
  async def service_run(
181
412
  *,
182
- project_id: ProjectIdOption = None,
413
+ project_id: ProjectIdOption,
183
414
  command: str,
184
415
  port: Annotated[
185
416
  int,
@@ -238,11 +469,26 @@ async def service_run(
238
469
  run_tasks = []
239
470
 
240
471
  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
- )
472
+ if command.endswith(".py"):
473
+ code, output = await _run_process(
474
+ cmd=shlex.split("python3 " + command),
475
+ log=True,
476
+ env={**os.environ, "MESHAGENT_PORT": str(port)},
477
+ )
478
+
479
+ elif command.endswith(".dart"):
480
+ code, output = await _run_process(
481
+ cmd=shlex.split("dart run " + command),
482
+ log=True,
483
+ env={**os.environ, "MESHAGENT_PORT": str(port)},
484
+ )
485
+
486
+ else:
487
+ code, output = await _run_process(
488
+ cmd=shlex.split(command),
489
+ log=True,
490
+ env={**os.environ, "MESHAGENT_PORT": str(port)},
491
+ )
246
492
 
247
493
  if code != 0:
248
494
  print(f"[red]{output}[/red]")
@@ -274,11 +520,12 @@ async def service_run(
274
520
  print("[red]unable to read service spec[/red]")
275
521
  raise typer.Exit(-1)
276
522
 
523
+ print(f"getting spec {port}", flush=True)
277
524
  spec = await get_spec(port)
278
525
 
279
526
  sys.stdout.write("\n")
280
527
 
281
- for p in spec.ports:
528
+ for p in spec.ports or []:
282
529
  print(f"[bold green]Connecting port {p.num}...[/bold green]")
283
530
 
284
531
  for endpoint in p.endpoints:
@@ -319,7 +566,7 @@ async def service_run(
319
566
  @app.async_command("show")
320
567
  async def service_show(
321
568
  *,
322
- project_id: ProjectIdOption = None,
569
+ project_id: ProjectIdOption,
323
570
  service_id: Annotated[str, typer.Argument(help="ID of the service to show")],
324
571
  ):
325
572
  """Show a services for the project."""
@@ -337,13 +584,11 @@ async def service_show(
337
584
  @app.async_command("list")
338
585
  async def service_list(
339
586
  *,
340
- project_id: ProjectIdOption = None,
587
+ project_id: ProjectIdOption,
341
588
  o: OutputFormatOption = "table",
342
589
  room: Annotated[
343
590
  Optional[str],
344
- typer.Option(
345
- "--room", "-r", help="The name of a room to list the services for"
346
- ),
591
+ typer.Option("--room", help="The name of a room to list the services for"),
347
592
  ] = None,
348
593
  ):
349
594
  """List all services for the project."""
@@ -359,9 +604,7 @@ async def service_list(
359
604
  )
360
605
 
361
606
  if o == "json":
362
- print(
363
- {"services": [svc.model_dump(mode="json") for svc in services]}
364
- ).model_dump_json(indent=2)
607
+ print({"services": [svc.model_dump(mode="json") for svc in services]})
365
608
  else:
366
609
  print_json_table(
367
610
  [
@@ -385,25 +628,14 @@ async def service_list(
385
628
  @app.async_command("delete")
386
629
  async def service_delete(
387
630
  *,
388
- project_id: ProjectIdOption = None,
631
+ project_id: ProjectIdOption,
389
632
  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
633
  ):
397
634
  """Delete a service."""
398
635
  client = await get_client()
399
636
  try:
400
637
  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
- )
638
+ await client.delete_service(project_id=project_id, service_id=service_id)
407
639
  print(f"[green]Service {service_id} deleted.[/]")
408
640
  finally:
409
641
  await client.close()
meshagent/cli/sessions.py CHANGED
@@ -2,11 +2,11 @@ from meshagent.cli import async_typer
2
2
  from meshagent.cli.helper import get_client, print_json_table, resolve_project_id
3
3
  from meshagent.cli.common_options import ProjectIdOption
4
4
 
5
- app = async_typer.AsyncTyper()
5
+ app = async_typer.AsyncTyper(help="Inspect recent sessions and events")
6
6
 
7
7
 
8
- @app.async_command("list")
9
- async def list(*, project_id: ProjectIdOption = None):
8
+ @app.async_command("list", help="List recent sessions")
9
+ async def list(*, project_id: ProjectIdOption):
10
10
  client = await get_client()
11
11
  sessions = await client.list_recent_sessions(
12
12
  project_id=await resolve_project_id(project_id=project_id)
@@ -15,8 +15,8 @@ async def list(*, project_id: ProjectIdOption = None):
15
15
  await client.close()
16
16
 
17
17
 
18
- @app.async_command("show")
19
- async def show(*, project_id: ProjectIdOption = None, session_id: str):
18
+ @app.async_command("show", help="Show events for a session")
19
+ async def show(*, project_id: ProjectIdOption, session_id: str):
20
20
  client = await get_client()
21
21
  events = await client.list_session_events(
22
22
  project_id=await resolve_project_id(project_id=project_id),
meshagent/cli/storage.py CHANGED
@@ -55,7 +55,7 @@ def split_glob_subpath(subpath: str):
55
55
  @app.async_command("exists")
56
56
  async def storage_exists_command(
57
57
  *,
58
- project_id: ProjectIdOption = None,
58
+ project_id: ProjectIdOption,
59
59
  room: RoomOption,
60
60
  path: str,
61
61
  ):
@@ -89,7 +89,7 @@ async def storage_exists_command(
89
89
  @app.async_command("cp")
90
90
  async def storage_cp_command(
91
91
  *,
92
- project_id: ProjectIdOption = None,
92
+ project_id: ProjectIdOption,
93
93
  room: RoomOption,
94
94
  source_path: str,
95
95
  dest_path: str,
@@ -322,7 +322,7 @@ async def storage_cp_command(
322
322
  @app.async_command("show")
323
323
  async def storage_show_command(
324
324
  *,
325
- project_id: ProjectIdOption = None,
325
+ project_id: ProjectIdOption,
326
326
  room: RoomOption,
327
327
  path: str,
328
328
  encoding: Annotated[
@@ -395,7 +395,7 @@ async def storage_show_command(
395
395
  @app.async_command("rm")
396
396
  async def storage_rm_command(
397
397
  *,
398
- project_id: ProjectIdOption = None,
398
+ project_id: ProjectIdOption,
399
399
  room: RoomOption,
400
400
  path: str,
401
401
  recursive: Annotated[
@@ -599,7 +599,7 @@ async def storage_rm_command(
599
599
  @app.async_command("ls")
600
600
  async def storage_ls_command(
601
601
  *,
602
- project_id: ProjectIdOption = None,
602
+ project_id: ProjectIdOption,
603
603
  room: RoomOption,
604
604
  path: Annotated[
605
605
  str, typer.Argument(..., help="Path to list (local or room://...)")