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/agent.py CHANGED
@@ -10,21 +10,23 @@ from meshagent.api import (
10
10
  RoomClient,
11
11
  WebSocketClientProtocol,
12
12
  RoomException,
13
+ TextResponse,
14
+ JsonResponse,
13
15
  )
14
16
  from meshagent.cli.helper import resolve_project_id
15
17
  from meshagent.cli import async_typer
16
18
  from meshagent.cli.helper import get_client, resolve_room
17
19
 
18
- app = async_typer.AsyncTyper()
20
+ app = async_typer.AsyncTyper(help="Interact with agents and toolkits in a room")
19
21
 
20
22
 
21
- @app.async_command("ask")
23
+ @app.async_command("ask", help="Send a request to an agent")
22
24
  async def ask(
23
25
  *,
24
- project_id: ProjectIdOption = None,
26
+ project_id: ProjectIdOption,
25
27
  room: RoomOption,
26
- agent: Annotated[str, typer.Option()],
27
- input: Annotated[str, typer.Option()],
28
+ agent: Annotated[str, typer.Option(..., help="Agent name to ask")],
29
+ input: Annotated[str, typer.Option(..., help="JSON string with tool arguments")],
28
30
  timeout: Annotated[
29
31
  Optional[int],
30
32
  typer.Option(
@@ -32,6 +34,8 @@ async def ask(
32
34
  ),
33
35
  ] = 30,
34
36
  ):
37
+ """Wait for an agent to join, then send it an ask request."""
38
+
35
39
  account_client = await get_client()
36
40
  try:
37
41
  project_id = await resolve_project_id(project_id=project_id)
@@ -69,17 +73,22 @@ async def ask(
69
73
  print("[magenta]Asking agent...[/magenta]")
70
74
 
71
75
  response = await client.agents.ask(agent=agent, arguments=json.loads(input))
72
- print(json.dumps(response.json))
76
+ if isinstance(response, TextResponse):
77
+ print(response.text)
78
+ elif isinstance(response, JsonResponse):
79
+ print(json.dumps(response.json))
80
+ else:
81
+ print(response)
73
82
  except RoomException as e:
74
83
  print(f"[red]{e}[/red]")
75
84
  finally:
76
85
  await account_client.close()
77
86
 
78
87
 
79
- @app.async_command("invoke-tool")
88
+ @app.async_command("invoke-tool", help="Invoke a specific tool from a toolkit")
80
89
  async def invoke_tool(
81
90
  *,
82
- project_id: ProjectIdOption = None,
91
+ project_id: ProjectIdOption,
83
92
  room: RoomOption,
84
93
  toolkit: Annotated[str, typer.Option(..., help="Toolkit name")],
85
94
  tool: Annotated[str, typer.Option(..., help="Tool name")],
@@ -161,10 +170,10 @@ async def invoke_tool(
161
170
  await account_client.close()
162
171
 
163
172
 
164
- @app.async_command("list-agents")
173
+ @app.async_command("list-agents", help="List agents currently in the room")
165
174
  async def list_agents_command(
166
175
  *,
167
- project_id: ProjectIdOption = None,
176
+ project_id: ProjectIdOption,
168
177
  room: RoomOption,
169
178
  ):
170
179
  """
@@ -194,7 +203,6 @@ async def list_agents_command(
194
203
  "name": agent.name,
195
204
  "title": agent.title,
196
205
  "description": agent.description,
197
- "requires": [r.to_json() for r in agent.requires],
198
206
  "supports_tools": agent.supports_tools,
199
207
  "labels": agent.labels,
200
208
  }
@@ -205,10 +213,12 @@ async def list_agents_command(
205
213
  await account_client.close()
206
214
 
207
215
 
208
- @app.async_command("list-toolkits")
216
+ @app.async_command(
217
+ "list-toolkits", help="List toolkits (and tools) available in the room"
218
+ )
209
219
  async def list_toolkits_command(
210
220
  *,
211
- project_id: ProjectIdOption = None,
221
+ project_id: ProjectIdOption,
212
222
  room: RoomOption,
213
223
  role: str = "user",
214
224
  participant_id: Annotated[
meshagent/cli/api_keys.py CHANGED
@@ -19,7 +19,7 @@ app = async_typer.AsyncTyper(help="Manage or activate api-keys for your project"
19
19
  @app.async_command("list")
20
20
  async def list(
21
21
  *,
22
- project_id: ProjectIdOption = None,
22
+ project_id: ProjectIdOption,
23
23
  o: OutputFormatOption = "table",
24
24
  ):
25
25
  project_id = await resolve_project_id(project_id=project_id)
@@ -42,7 +42,7 @@ async def list(
42
42
  @app.async_command("create")
43
43
  async def create(
44
44
  *,
45
- project_id: ProjectIdOption = None,
45
+ project_id: ProjectIdOption,
46
46
  name: str,
47
47
  description: Annotated[
48
48
  str, typer.Option(..., help="a description for the api key")
@@ -85,7 +85,7 @@ async def create(
85
85
  @app.async_command("activate")
86
86
  async def activate(
87
87
  *,
88
- project_id: ProjectIdOption = None,
88
+ project_id: ProjectIdOption,
89
89
  key: str,
90
90
  ):
91
91
  project_id = await resolve_project_id(project_id=project_id)
@@ -94,7 +94,7 @@ async def activate(
94
94
 
95
95
 
96
96
  @app.async_command("delete")
97
- async def delete(*, project_id: ProjectIdOption = None, id: str):
97
+ async def delete(*, project_id: ProjectIdOption, id: str):
98
98
  project_id = await resolve_project_id(project_id=project_id)
99
99
 
100
100
  client = await get_client()
@@ -2,20 +2,64 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import inspect
5
+ import threading
5
6
  from functools import partial, wraps
6
- from typing import Any, Callable
7
+ from typing import Any, Callable, TypeVar
7
8
 
8
9
  from typer import Typer
9
10
 
11
+ T = TypeVar("T")
12
+
13
+
14
+ def _run_coroutine_sync(
15
+ coro: "asyncio.Future[T] | asyncio.coroutines.Coroutine[Any, Any, T]",
16
+ ) -> T:
17
+ """
18
+ Run an awaitable from sync code.
19
+
20
+ - If we're not currently in an event loop, use asyncio.run().
21
+ - If we ARE in a running loop (e.g. inside an agent / notebook / ASGI app),
22
+ run asyncio.run() in a separate thread and block for the result.
23
+
24
+ This avoids: RuntimeError: asyncio.run() cannot be called from a running event loop
25
+ """
26
+ try:
27
+ asyncio.get_running_loop()
28
+ in_running_loop = True
29
+ except RuntimeError:
30
+ in_running_loop = False
31
+
32
+ if not in_running_loop:
33
+ return asyncio.run(coro) # type: ignore[arg-type]
34
+
35
+ result: dict[str, Any] = {}
36
+ done = threading.Event()
37
+
38
+ def _worker() -> None:
39
+ try:
40
+ result["value"] = asyncio.run(coro) # type: ignore[arg-type]
41
+ except BaseException as e:
42
+ result["error"] = e
43
+ finally:
44
+ done.set()
45
+
46
+ t = threading.Thread(target=_worker, daemon=True)
47
+ t.start()
48
+ done.wait()
49
+
50
+ if "error" in result:
51
+ raise result["error"]
52
+ return result["value"] # type: ignore[return-value]
53
+
10
54
 
11
55
  class AsyncTyper(Typer):
12
56
  @staticmethod
13
- def maybe_run_async(decorator: Callable, func: Callable) -> Any:
57
+ def maybe_run_async(decorator: Callable[..., Any], func: Callable[..., Any]) -> Any:
14
58
  if inspect.iscoroutinefunction(func):
15
59
 
16
60
  @wraps(func)
17
61
  def runner(*args: Any, **kwargs: Any) -> Any:
18
- return asyncio.run(func(*args, **kwargs))
62
+ return _run_coroutine_sync(func(*args, **kwargs))
19
63
 
20
64
  decorator(runner)
21
65
  else:
@@ -26,6 +70,10 @@ class AsyncTyper(Typer):
26
70
  decorator = super().callback(*args, **kwargs)
27
71
  return partial(self.maybe_run_async, decorator)
28
72
 
29
- def async_command(self, *args: Any, **kwargs: Any) -> Any:
73
+ def command(self, *args: Any, **kwargs: Any) -> Any:
30
74
  decorator = super().command(*args, **kwargs)
31
75
  return partial(self.maybe_run_async, decorator)
76
+
77
+ # keep your existing name if you prefer
78
+ def async_command(self, *args: Any, **kwargs: Any) -> Any:
79
+ return self.command(*args, **kwargs)
meshagent/cli/call.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import typer
2
+ import os
2
3
  from rich import print
3
4
  from typing import Annotated, Optional
4
5
  from meshagent.cli.common_options import ProjectIdOption, RoomOption
@@ -24,7 +25,7 @@ import pathlib
24
25
  from pydantic_yaml import parse_yaml_raw_as
25
26
  from meshagent.api.participant_token import ParticipantTokenSpec
26
27
 
27
- app = async_typer.AsyncTyper()
28
+ app = async_typer.AsyncTyper(help="Trigger agent/tool calls via URL")
28
29
 
29
30
  PRIVATE_NETS = (
30
31
  ipaddress.ip_network("10.0.0.0/8"),
@@ -68,20 +69,16 @@ def is_local_url(url: str) -> bool:
68
69
  return True
69
70
 
70
71
 
71
- @app.async_command("schema")
72
- @app.async_command("toolkit")
73
- @app.async_command("agent")
74
- @app.async_command("tool")
72
+ @app.async_command("schema", help="Send a call request to a schema webhook URL")
73
+ @app.async_command("toolkit", help="Send a call request to a toolkit webhook URL")
74
+ @app.async_command("agent", help="Send a call request to an agent webhook URL")
75
+ @app.async_command("tool", help="Send a call request to a tool webhook URL")
75
76
  async def make_call(
76
77
  *,
77
- project_id: ProjectIdOption = None,
78
+ project_id: ProjectIdOption,
78
79
  room: RoomOption,
79
80
  role: str = "agent",
80
81
  local: Optional[bool] = None,
81
- agent_name: Annotated[
82
- Optional[str], typer.Option(..., help="deprecated and unused", hidden=True)
83
- ] = None,
84
- name: Annotated[str, typer.Option(..., help="deprecated", hidden=True)] = None,
85
82
  participant_name: Annotated[
86
83
  Optional[str],
87
84
  typer.Option(..., help="the participant name to be used by the callee"),
@@ -103,6 +100,8 @@ async def make_call(
103
100
  typer.Option("--key", help="an api key to sign the token with"),
104
101
  ] = None,
105
102
  ):
103
+ """Send a `room.call` request to a URL or in-room agent."""
104
+
106
105
  key = await resolve_key(project_id=project_id, key=key)
107
106
 
108
107
  if permissions is not None:
@@ -124,8 +123,6 @@ async def make_call(
124
123
  room=room,
125
124
  role=role,
126
125
  local=local,
127
- agent_name=agent_name,
128
- name=name,
129
126
  participant_name=participant_name,
130
127
  url=url,
131
128
  arguments=arguments,
@@ -136,14 +133,10 @@ async def make_call(
136
133
 
137
134
  async def _make_call(
138
135
  *,
139
- project_id: ProjectIdOption = None,
136
+ project_id: ProjectIdOption,
140
137
  room: RoomOption,
141
138
  role: str = "agent",
142
139
  local: Optional[bool] = None,
143
- agent_name: Annotated[
144
- Optional[str], typer.Option(..., help="deprecated and unused", hidden=True)
145
- ] = None,
146
- name: Annotated[str, typer.Option(..., help="deprecated", hidden=True)] = None,
147
140
  participant_name: Annotated[
148
141
  Optional[str],
149
142
  typer.Option(..., help="the participant name to be used by the callee"),
@@ -160,15 +153,6 @@ async def _make_call(
160
153
  Instruct an agent to 'call' a given URL with specific arguments.
161
154
 
162
155
  """
163
- if name is not None:
164
- print("[yellow]name is deprecated and should no longer be passed[/yellow]")
165
-
166
- if agent_name is not None:
167
- print(
168
- "[yellow]agent-name is deprecated and should no longer be passed, use participant-name instead[/yellow]"
169
- )
170
- participant_name = agent_name
171
-
172
156
  if participant_name is None:
173
157
  print("[red]--participant-name is required[/red]")
174
158
  raise typer.Exit(1)
@@ -180,13 +164,18 @@ async def _make_call(
180
164
  room = resolve_room(room)
181
165
 
182
166
  if token is None:
183
- token = ParticipantToken(
184
- name=participant_name,
185
- )
186
- token.add_api_grant(permissions or ApiScope.agent_default())
187
- token.add_role_grant(role=role)
188
- token.add_room_grant(room)
189
- token.grants.append(ParticipantGrant(name="tunnel_ports", scope="9000"))
167
+ jwt = os.getenv("MESHAGENT_TOKEN")
168
+ if jwt is None:
169
+ token = ParticipantToken(
170
+ name=participant_name,
171
+ )
172
+ token.add_api_grant(permissions or ApiScope.agent_default())
173
+ token.add_role_grant(role=role)
174
+ token.add_room_grant(room)
175
+ token.grants.append(ParticipantGrant(name="tunnel_ports", scope="9000"))
176
+ jwt = token.to_jwt(api_key=key)
177
+ else:
178
+ jwt = token.to_jwt(api_key=key)
190
179
 
191
180
  if local is None:
192
181
  local = is_local_url(url)
@@ -197,8 +186,10 @@ async def _make_call(
197
186
  data = {
198
187
  "room_url": websocket_room_url(room_name=room),
199
188
  "room_name": room,
200
- "token": token.to_jwt(api_key=key),
201
- "arguments": arguments,
189
+ "token": jwt,
190
+ "arguments": json.loads(arguments)
191
+ if isinstance(arguments, str)
192
+ else arguments,
202
193
  }
203
194
 
204
195
  await send_webhook(
@@ -211,7 +202,7 @@ async def _make_call(
211
202
  url=websocket_room_url(
212
203
  room_name=room, base_url=meshagent_base_url()
213
204
  ),
214
- token=token.to_jwt(api_key=key),
205
+ token=jwt,
215
206
  )
216
207
  ) as client:
217
208
  print("[bold green]Making agent call...[/bold green]")