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
@@ -14,13 +14,13 @@ from meshagent.api import (
14
14
  )
15
15
  from meshagent.api.helpers import meshagent_base_url, websocket_room_url
16
16
 
17
- app = async_typer.AsyncTyper()
17
+ app = async_typer.AsyncTyper(help="Developer utilities for a room")
18
18
 
19
19
 
20
- @app.async_command("watch")
20
+ @app.async_command("watch", help="Stream developer logs from a room")
21
21
  async def watch_logs(
22
22
  *,
23
- project_id: ProjectIdOption = None,
23
+ project_id: ProjectIdOption,
24
24
  room: RoomOption,
25
25
  ):
26
26
  """
meshagent/cli/exec.py CHANGED
@@ -82,13 +82,29 @@ def register(app: typer.Typer):
82
82
  @app.async_command("exec")
83
83
  async def exec_command(
84
84
  *,
85
- project_id: ProjectIdOption = None,
85
+ project_id: ProjectIdOption,
86
86
  room: RoomOption,
87
- name: Annotated[Optional[str], typer.Option()] = None,
88
- image: Annotated[Optional[str], typer.Option()] = None,
89
- command: Annotated[list[str], typer.Argument(...)] = None,
90
- tty: bool = False,
91
- room_storage_path: str = "/data",
87
+ name: Annotated[
88
+ Optional[str], typer.Option(help="Optional exec session name")
89
+ ] = None,
90
+ image: Annotated[
91
+ Optional[str],
92
+ typer.Option(help="Optional container image to use for the exec session"),
93
+ ] = None,
94
+ command: Annotated[
95
+ list[str],
96
+ typer.Argument(..., help="Command to execute (omit when using `--tty`)"),
97
+ ] = None,
98
+ tty: Annotated[
99
+ bool,
100
+ typer.Option(
101
+ "--tty/--no-tty",
102
+ help="Allocate an interactive TTY (requires a real terminal)",
103
+ ),
104
+ ] = False,
105
+ room_storage_path: Annotated[
106
+ str, typer.Option(help="Room storage mount path (default: /data)")
107
+ ] = "/data",
92
108
  ):
93
109
  """Open an interactive websocket‑based TTY."""
94
110
  client = await get_client()
meshagent/cli/helper.py CHANGED
@@ -7,8 +7,14 @@ from typing import Optional
7
7
  from meshagent.cli import auth_async
8
8
  from meshagent.cli import async_typer
9
9
  from meshagent.api.helpers import meshagent_base_url
10
- from meshagent.api.client import Meshagent
10
+ from meshagent.api.specs.service import ServiceSpec
11
+ from meshagent.agents.context import AgentChatContext
12
+ from meshagent.api.client import Meshagent, RoomConnectionInfo
11
13
  import os
14
+ import aiofiles
15
+ from pydantic_yaml import parse_yaml_raw_as
16
+ import json
17
+
12
18
  from rich import print
13
19
 
14
20
  SETTINGS_FILE = Path.home() / ".meshagent" / "project.json"
@@ -29,11 +35,15 @@ def _save_settings(s: Settings):
29
35
 
30
36
 
31
37
  def _load_settings():
32
- _ensure_cache_dir()
33
- if SETTINGS_FILE.exists():
34
- return Settings.model_validate_json(SETTINGS_FILE.read_text())
35
-
36
- return Settings()
38
+ try:
39
+ _ensure_cache_dir()
40
+ if SETTINGS_FILE.exists():
41
+ return Settings.model_validate_json(SETTINGS_FILE.read_text())
42
+ except OSError as ex:
43
+ if ex.errno == 30:
44
+ return Settings()
45
+ else:
46
+ raise
37
47
 
38
48
 
39
49
  async def get_active_project():
@@ -57,6 +67,8 @@ async def set_active_api_key(project_id: str, key: str):
57
67
 
58
68
  async def get_active_api_key(project_id: str):
59
69
  settings = _load_settings()
70
+ if settings is None:
71
+ return None
60
72
  key: str = settings.active_api_keys.get(project_id)
61
73
  # Ignore old keys, API key format changed
62
74
  if key is not None and key.startswith("ma-"):
@@ -68,16 +80,33 @@ async def get_active_api_key(project_id: str):
68
80
  app = async_typer.AsyncTyper()
69
81
 
70
82
 
83
+ class CustomMeshagentClient(Meshagent):
84
+ async def connect_room(self, *, project_id: str, room: str) -> RoomConnectionInfo:
85
+ from urllib.parse import quote
86
+
87
+ jwt = os.getenv("MESHAGENT_TOKEN")
88
+
89
+ if jwt is not None and room == os.getenv("MESHAGENT_ROOM"):
90
+ return RoomConnectionInfo(
91
+ jwt=jwt,
92
+ room_name=room,
93
+ project_id=os.getenv("MESHAGENT_PROJECT_ID"),
94
+ room_url=meshagent_base_url() + f"/rooms/{quote(room)}",
95
+ )
96
+
97
+ return await super().connect_room(project_id=project_id, room=room)
98
+
99
+
71
100
  async def get_client():
72
101
  key = os.getenv("MESHAGENT_API_KEY")
73
- if key is not None:
74
- return Meshagent(
102
+ if key is not None or os.getenv("MESHAGENT_SESSION_ID") is not None:
103
+ return CustomMeshagentClient(
75
104
  base_url=meshagent_base_url(),
76
105
  token=key,
77
106
  )
78
107
  else:
79
108
  access_token = await auth_async.get_access_token()
80
- return Meshagent(
109
+ return CustomMeshagentClient(
81
110
  base_url=meshagent_base_url(),
82
111
  token=access_token,
83
112
  )
@@ -119,7 +148,7 @@ def resolve_room(room_name: Optional[str] = None):
119
148
 
120
149
  async def resolve_project_id(project_id: Optional[str] = None):
121
150
  if project_id is None:
122
- project_id = await get_active_project()
151
+ project_id = os.getenv("MESHAGENT_PROJECT_ID") or await get_active_project()
123
152
 
124
153
  if project_id is None:
125
154
  print(
@@ -130,6 +159,32 @@ async def resolve_project_id(project_id: Optional[str] = None):
130
159
  return project_id
131
160
 
132
161
 
162
+ async def init_context_from_spec(context: AgentChatContext) -> None:
163
+ path = os.getenv("MESHAGENT_SPEC_PATH")
164
+
165
+ if path is None:
166
+ return None
167
+
168
+ async with aiofiles.open(path, "r") as file:
169
+ spec_str = await file.read()
170
+ try:
171
+ json.loads(spec_str)
172
+ spec = ServiceSpec.model_validate_json(spec_str)
173
+ except ValueError:
174
+ # fallback on yaml parser if spec can't
175
+ spec = parse_yaml_raw_as(ServiceSpec, spec_str)
176
+
177
+ readme = spec.metadata.annotations.get("meshagent.service.readme")
178
+
179
+ if spec.metadata.description:
180
+ context.append_assistant_message(
181
+ f"This agent's description:\n{spec.metadata.description}"
182
+ )
183
+
184
+ if readme is not None:
185
+ context.append_assistant_message(f"This agent's README:\n{readme}")
186
+
187
+
133
188
  async def resolve_key(project_id: str | None, key: str | None):
134
189
  project_id = await resolve_project_id(project_id=project_id)
135
190
  if key is None:
@@ -138,10 +193,44 @@ async def resolve_key(project_id: str | None, key: str | None):
138
193
  if key is None:
139
194
  key = os.getenv("MESHAGENT_API_KEY")
140
195
 
141
- if key is None:
196
+ if key is None and os.getenv("MESHAGENT_TOKEN") is None:
142
197
  print(
143
- "[red]--key is required if MESHGENT_API_KEY is not set. You can use meshagent api-key create to create a new api key."
198
+ "[red]--key is required if MESHAGENT_API_KEY is not set. You can use meshagent api-key create to create a new api key."
144
199
  )
145
200
  raise typer.Exit(1)
146
201
 
147
202
  return key
203
+
204
+
205
+ def cleanup_args(args: list[str]):
206
+ out = []
207
+ i = 0
208
+ while i < len(args):
209
+ if args[i] == "--service-name":
210
+ i += 1
211
+ elif args[i] == "--service-title":
212
+ i += 1
213
+ elif args[i] == "--service-description":
214
+ i += 1
215
+ elif args[i] == "--project-id":
216
+ i += 1
217
+ elif args[i] == "--room":
218
+ i += 1
219
+ elif args[i].startswith("--service-name="):
220
+ pass
221
+ elif args[i].startswith("--service-title="):
222
+ pass
223
+ elif args[i].startswith("--service-description="):
224
+ pass
225
+ elif args[i].startswith("--project-id="):
226
+ pass
227
+ elif args[i].startswith("--room="):
228
+ pass
229
+ elif args[i] == "deploy":
230
+ pass
231
+ elif args[i] == "spec":
232
+ pass
233
+ else:
234
+ out.append(args[i])
235
+ i += 1
236
+ return out
meshagent/cli/helpers.py CHANGED
@@ -1,19 +1,20 @@
1
1
  from meshagent.cli import async_typer
2
2
 
3
-
4
3
  from meshagent.api import SchemaRegistry, SchemaRegistration
5
4
 
6
5
 
7
6
  import logging
8
7
 
9
- app = async_typer.AsyncTyper(help="Join a mailbot to a room")
8
+ app = async_typer.AsyncTyper(help="Developer helper services")
10
9
 
11
10
 
12
- @app.async_command("service")
11
+ @app.async_command("service", help="Run local helper HTTP services")
13
12
  async def helpers_service():
14
- from meshagent.agents.planning import DynamicPlanningResponder, PlanningResponder
13
+ """Run local helper services (agents, schemas, toolkits)."""
14
+ from meshagent.agents.llmrunner import LLMTaskRunner, DynamicLLMTaskRunner
15
15
  from meshagent.openai.tools import OpenAIResponsesAdapter
16
16
  from meshagent.tools.storage import StorageToolkit
17
+ from meshagent.tools.database import DatabaseToolkitBuilder
17
18
  from meshagent.api.services import ServiceHost
18
19
 
19
20
  from meshagent.agents.schemas.gallery import gallery_schema
@@ -24,20 +25,51 @@ async def helpers_service():
24
25
  )
25
26
  from meshagent.agents.schemas.presentation import presentation_schema
26
27
  from meshagent.agents import thread_schema
28
+ from meshagent.agents.widget_schema import widget_schema
27
29
 
28
30
  logging.getLogger("openai").setLevel(logging.ERROR)
29
31
  logging.getLogger("httpx").setLevel(logging.ERROR)
30
32
 
31
33
  service = ServiceHost(port=9000)
32
34
 
33
- @service.path("/planner")
34
- class Planner(PlanningResponder):
35
+ @service.path("/runner")
36
+ class Runner(LLMTaskRunner):
35
37
  def __init__(self, **kwargs):
36
38
  super().__init__(
37
- name="meshagent.planner",
38
39
  title="Generic Task Runner",
39
40
  description="an agent that will perform a task with the selected tools",
40
- llm_adapter=OpenAIResponsesAdapter(model="gpt-4.1"),
41
+ llm_adapter=OpenAIResponsesAdapter(model="gpt-5.2"),
42
+ supports_tools=True,
43
+ input_prompt=True,
44
+ output_schema={
45
+ "type": "object",
46
+ "required": ["result"],
47
+ "additionalProperties": False,
48
+ "properties": {"result": {"type": "string"}},
49
+ },
50
+ annotations={"meshagent.task-runner.attachment-format": "tar"},
51
+ )
52
+
53
+ def get_toolkit_builders(self):
54
+ from meshagent.tools.storage import StorageToolkitBuilder
55
+ from meshagent.openai.tools.responses_adapter import WebSearchToolkitBuilder
56
+
57
+ providers = [
58
+ WebSearchToolkitBuilder(),
59
+ StorageToolkitBuilder(),
60
+ DatabaseToolkitBuilder(),
61
+ *super().get_toolkit_builders(),
62
+ ]
63
+
64
+ return providers
65
+
66
+ @service.path("/planner")
67
+ class Planner(LLMTaskRunner):
68
+ def __init__(self, **kwargs):
69
+ super().__init__(
70
+ title="Generic Task Runner (Legacy)",
71
+ description="an agent that will perform a task with the selected tools",
72
+ llm_adapter=OpenAIResponsesAdapter(model="gpt-5.2"),
41
73
  supports_tools=True,
42
74
  input_prompt=True,
43
75
  output_schema={
@@ -49,15 +81,26 @@ async def helpers_service():
49
81
  )
50
82
 
51
83
  @service.path("/schema_planner")
52
- class DynamicPlanner(DynamicPlanningResponder):
84
+ class DynamicPlanner(DynamicLLMTaskRunner):
53
85
  def __init__(self, **kwargs):
54
86
  super().__init__(
55
- name="meshagent.schema_planner",
56
87
  title="Schema Task Runner",
57
88
  description="an agent that can produces output that matches a schema",
58
- llm_adapter=OpenAIResponsesAdapter(model="gpt-4.1"),
89
+ llm_adapter=OpenAIResponsesAdapter(model="gpt-5.2"),
59
90
  )
60
91
 
92
+ def get_toolkit_builders(self):
93
+ from meshagent.tools.storage import StorageToolkitBuilder
94
+ from meshagent.openai.tools.responses_adapter import WebSearchToolkitBuilder
95
+
96
+ providers = [
97
+ WebSearchToolkitBuilder(),
98
+ StorageToolkitBuilder(),
99
+ *super().get_toolkit_builders(),
100
+ ]
101
+
102
+ return providers
103
+
61
104
  @service.path("/schemas/document")
62
105
  class DocumentSchemaRegistry(SchemaRegistry):
63
106
  def __init__(self):
@@ -102,6 +145,17 @@ async def helpers_service():
102
145
  schemas=[SchemaRegistration(name=name, schema=schema)],
103
146
  )
104
147
 
148
+ @service.path("/schemas/widget")
149
+ class WidgetDocumentSchemaRegistry(SchemaRegistry):
150
+ def __init__(self):
151
+ name = "widget"
152
+ schema = widget_schema
153
+ super().__init__(
154
+ name=f"meshagent.schema.{name}",
155
+ validate_webhook_secret=False,
156
+ schemas=[SchemaRegistration(name=name, schema=schema)],
157
+ )
158
+
105
159
  @service.path("/schemas/presentation")
106
160
  class PresentationDocumentSchemaRegistry(SchemaRegistry):
107
161
  def __init__(presentation):
meshagent/cli/host.py ADDED
@@ -0,0 +1,41 @@
1
+ from meshagent.api.services import ServiceHost
2
+ from meshagent.api.specs.service import ServiceSpec
3
+ import asyncio
4
+ from meshagent.agents import Agent
5
+
6
+
7
+ options = {"deferred": False}
8
+ services = {}
9
+
10
+
11
+ agents: list[tuple[Agent, str]] = []
12
+
13
+
14
+ def set_deferred(deferred: bool):
15
+ options["deferred"] = deferred
16
+
17
+
18
+ def get_deferred() -> bool:
19
+ return options["deferred"]
20
+
21
+
22
+ def get_service(port: int, host: str) -> ServiceHost:
23
+ if port not in services:
24
+ services[port] = ServiceHost(host=host, port=port)
25
+
26
+ return services[port]
27
+
28
+
29
+ def service_specs() -> list[ServiceSpec]:
30
+ specs = []
31
+ for port, s in services.items():
32
+ specs.append(s.get_service_spec(image=""))
33
+ return specs
34
+
35
+
36
+ async def run_services():
37
+ tasks = []
38
+ for port, s in services.items():
39
+ tasks.append(s.run())
40
+
41
+ await asyncio.gather(*tasks)