meshagent-cli 0.21.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.
meshagent/cli/multi.py CHANGED
@@ -10,45 +10,54 @@ import click
10
10
  import shlex
11
11
 
12
12
  from rich import print
13
- from meshagent.cli.call import _make_call
14
- import sys
13
+
14
+ from meshagent.agents import Agent
15
15
 
16
16
  from typer.main import get_command
17
17
 
18
+ from meshagent.cli.common_options import RoomOption
18
19
  from meshagent.cli.helper import (
19
20
  get_client,
20
21
  resolve_project_id,
21
- resolve_room,
22
- resolve_key,
23
- )
24
- from meshagent.api import (
25
- ParticipantToken,
26
- ApiScope,
27
22
  )
28
23
  from aiohttp import ClientResponseError
29
24
  import asyncio
30
25
 
31
- from meshagent.api.keys import parse_api_key
26
+ from meshagent.api import RoomClient
27
+
28
+ from meshagent.api.helpers import meshagent_base_url, websocket_room_url
29
+ from meshagent.api.websocket_protocol import WebSocketClientProtocol
32
30
 
33
31
  from meshagent.cli.chatbot import service as chatbot_service
34
32
  from meshagent.cli.worker import service as worker_service
35
33
  from meshagent.cli.mailbot import service as mailbot_service
36
34
  from meshagent.cli.voicebot import service as voicebot_service
37
35
 
36
+ from meshagent.cli.chatbot import join as chatbot_join
37
+ from meshagent.cli.worker import join as worker_join
38
+ from meshagent.cli.mailbot import join as mailbot_join
39
+ from meshagent.cli.voicebot import join as voicebot_join
40
+
38
41
  import yaml
39
42
 
40
43
 
41
44
  app = async_typer.AsyncTyper(help="Connect agents and tools to a room")
42
45
 
43
- cli = async_typer.AsyncTyper(help="Add agents to a team")
46
+ cli_service = async_typer.AsyncTyper(help="Add agents to a team")
44
47
 
45
- cli.command("chatbot")(chatbot_service)
46
- cli.command("worker")(worker_service)
47
- cli.command("mailbot")(mailbot_service)
48
- cli.command("voicebot")(voicebot_service)
48
+ cli_service.command("chatbot")(chatbot_service)
49
+ cli_service.command("worker")(worker_service)
50
+ cli_service.command("mailbot")(mailbot_service)
51
+ cli_service.command("voicebot")(voicebot_service)
49
52
 
53
+ cli_join = async_typer.AsyncTyper(help="Add agents to a team")
54
+ cli_join.command("chatbot")(chatbot_join)
55
+ cli_join.command("worker")(worker_join)
56
+ cli_join.command("mailbot")(mailbot_join)
57
+ cli_join.command("voicebot")(voicebot_join)
50
58
 
51
- @cli.async_command("python")
59
+
60
+ @cli_service.async_command("python")
52
61
  async def python(
53
62
  *,
54
63
  module: str,
@@ -120,7 +129,7 @@ def build_spec(
120
129
  ] = None,
121
130
  ):
122
131
  for c in command.split(";"):
123
- if execute_via_root(cli, c, prog_name="meshagent") != 0:
132
+ if execute_via_root(cli_service, c, prog_name="meshagent") != 0:
124
133
  print(f"[red]{c} failed[/red]")
125
134
  raise typer.Exit(1)
126
135
 
@@ -271,7 +280,7 @@ async def host(
271
280
  set_deferred(True)
272
281
 
273
282
  for c in command.split(";"):
274
- if execute_via_root(cli, c, prog_name="meshagent") != 0:
283
+ if execute_via_root(cli_service, c, prog_name="meshagent") != 0:
275
284
  print(f"[red]{c} failed[/red]")
276
285
  raise typer.Exit(1)
277
286
 
@@ -306,12 +315,7 @@ async def join(
306
315
  ),
307
316
  ),
308
317
  ] = None,
309
- room: Annotated[
310
- Optional[str],
311
- typer.Option(
312
- help="A room name to test the service in (must not be currently running)"
313
- ),
314
- ] = None,
318
+ room: RoomOption,
315
319
  key: Annotated[
316
320
  str,
317
321
  typer.Option("--key", help="an api key to sign the token with"),
@@ -319,84 +323,35 @@ async def join(
319
323
  ):
320
324
  set_deferred(True)
321
325
 
322
- for c in command.split(";"):
323
- if execute_via_root(cli, c, prog_name="meshagent") != 0:
324
- print(f"[red]{c} failed[/red]")
325
- raise typer.Exit(1)
326
-
327
- services_task = asyncio.create_task(run_services())
326
+ if room is None:
327
+ print("[bold red]--room is required[/bold red]")
328
+ raise typer.Exit(-1)
328
329
 
329
- key = await resolve_key(project_id=project_id, key=key)
330
-
331
- if port is None:
332
- import socket
333
-
334
- def find_free_port():
335
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
336
- s.bind(("", 0)) # Bind to a free port provided by the host.
337
- s.listen(1)
338
- return s.getsockname()[1]
330
+ for c in command.split(";"):
331
+ execute_via_root(cli_join, c + f" --room={room}", prog_name="meshagent")
339
332
 
340
- port = find_free_port()
333
+ from meshagent.cli.host import agents
341
334
 
342
- my_client = await get_client()
343
335
  try:
344
- project_id = await resolve_project_id(project_id)
345
- room = resolve_room(room)
346
-
347
- if room is None:
348
- print("[bold red]Room was not set[/bold red]")
349
- raise typer.Exit(1)
350
-
351
- try:
352
- parsed_key = parse_api_key(key)
353
- token = ParticipantToken(
354
- name="cli", project_id=project_id, api_key_id=parsed_key.id
355
- )
356
- token.add_api_grant(ApiScope.agent_default())
357
- token.add_role_grant("user")
358
- token.add_room_grant(room)
359
-
360
- print("[bold green]Connecting to room...[/bold green]")
361
-
362
- run_tasks = []
363
-
364
- for spec in service_specs():
365
- sys.stdout.write("\n")
366
-
367
- for p in spec.ports:
368
- print(f"[bold green]Connecting port {p.num}...[/bold green]")
369
-
370
- for endpoint in p.endpoints:
371
- print(
372
- f"[bold green]Connecting endpoint {endpoint.path}...[/bold green]"
373
- )
374
-
375
- run_tasks.append(
376
- asyncio.create_task(
377
- _make_call(
378
- room=room,
379
- project_id=project_id,
380
- participant_name=endpoint.meshagent.identity,
381
- url=f"http://localhost:{p.num}{endpoint.path}",
382
- arguments={},
383
- key=key,
384
- permissions=endpoint.meshagent.api,
385
- )
386
- )
387
- )
388
-
389
- await asyncio.gather(*run_tasks, services_task)
390
336
 
391
- except ClientResponseError as exc:
392
- if exc.status == 409:
393
- print(f"[red]Room already in use: {room}[/red]")
394
- raise typer.Exit(code=1)
395
- raise
396
-
397
- except Exception as e:
398
- print(f"[red]{e}[/red]")
399
- raise typer.Exit(code=1)
337
+ async def run_agent(agent: Agent, jwt: str):
338
+ nonlocal room
339
+
340
+ async with RoomClient(
341
+ protocol=WebSocketClientProtocol(
342
+ url=websocket_room_url(
343
+ room_name=room, base_url=meshagent_base_url()
344
+ ),
345
+ token=jwt,
346
+ )
347
+ ) as room:
348
+ await agent.start(room=room)
349
+ await room.protocol.wait_for_close()
350
+ await agent.stop()
351
+
352
+ await asyncio.gather(
353
+ *([asyncio.create_task(run_agent(agent, jwt)) for agent, jwt in agents])
354
+ )
400
355
 
401
- finally:
402
- await my_client.close()
356
+ except KeyboardInterrupt:
357
+ pass
meshagent/cli/oauth2.py CHANGED
@@ -9,13 +9,29 @@ from meshagent.api import RoomClient, WebSocketClientProtocol
9
9
  from meshagent.api.helpers import meshagent_base_url, websocket_room_url
10
10
  from rich import print
11
11
  from typing import Annotated, Optional
12
+ from pathlib import Path
12
13
  import typer
13
14
  import json
15
+ import sys
14
16
 
15
17
  app = async_typer.AsyncTyper(help="OAuth2 test commands")
16
18
 
17
19
 
18
- @app.async_command("request")
20
+ def _read_bytes(*, input_path: str) -> bytes:
21
+ if input_path == "-":
22
+ return sys.stdin.buffer.read()
23
+ return Path(input_path).expanduser().resolve().read_bytes()
24
+
25
+
26
+ def _write_bytes(*, output_path: str, data: bytes) -> None:
27
+ if output_path == "-":
28
+ sys.stdout.buffer.write(data)
29
+ sys.stdout.buffer.flush()
30
+ return
31
+ Path(output_path).expanduser().resolve().write_bytes(data)
32
+
33
+
34
+ @app.async_command("oauth")
19
35
  async def oauth2(
20
36
  *,
21
37
  project_id: ProjectIdOption,
@@ -80,39 +96,157 @@ async def oauth2(
80
96
  await account_client.close()
81
97
 
82
98
 
99
+ @app.async_command("request")
100
+ async def secret_request(
101
+ *,
102
+ project_id: ProjectIdOption,
103
+ room: RoomOption,
104
+ from_participant_id: Annotated[
105
+ str,
106
+ typer.Option(..., help="Participant ID to request the secret from"),
107
+ ],
108
+ url: Annotated[str, typer.Option(..., help="Secret URL identifier")],
109
+ mime_type: Annotated[
110
+ str, typer.Option("--type", help="Secret MIME type")
111
+ ] = "application/octet-stream",
112
+ delegate_to: Annotated[
113
+ Optional[str],
114
+ typer.Option(help="Delegate secret to this participant name"),
115
+ ] = None,
116
+ timeout: Annotated[int, typer.Option(help="Timeout in seconds")] = 300,
117
+ out: Annotated[
118
+ str,
119
+ typer.Option(
120
+ "--out",
121
+ "-o",
122
+ help="Output file path, or '-' for stdout",
123
+ ),
124
+ ] = "-",
125
+ ):
126
+ """Request a secret from another participant."""
127
+
128
+ account_client = await get_client()
129
+ try:
130
+ project_id = await resolve_project_id(project_id=project_id)
131
+ jwt_consumer = await account_client.connect_room(
132
+ project_id=project_id, room=room
133
+ )
134
+
135
+ async with RoomClient(
136
+ protocol=WebSocketClientProtocol(
137
+ url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
138
+ token=jwt_consumer.jwt,
139
+ )
140
+ ) as consumer:
141
+ typer.echo(
142
+ f"Requesting secret from participant {from_participant_id}...",
143
+ err=True,
144
+ )
145
+ secret = await consumer.secrets.request_secret(
146
+ url=url,
147
+ type=mime_type,
148
+ timeout=timeout,
149
+ from_participant_id=from_participant_id,
150
+ delegate_to=delegate_to,
151
+ )
152
+
153
+ _write_bytes(output_path=out, data=secret)
154
+ if out != "-":
155
+ typer.echo(f"Wrote {len(secret)} bytes to {out}", err=True)
156
+
157
+ finally:
158
+ await account_client.close()
159
+
160
+
83
161
  @app.async_command("get")
84
- async def get(
162
+ async def secret_get(
85
163
  *,
86
164
  project_id: ProjectIdOption,
87
165
  room: RoomOption,
166
+ secret_id: Annotated[str, typer.Option(..., help="Secret ID")],
88
167
  delegated_to: Annotated[
89
- str, typer.Option(..., help="Participant ID to delegate the token to")
90
- ],
91
- client_id: Annotated[str, typer.Option(..., help="OAuth client ID")],
92
- authorization_endpoint: Annotated[
93
- str, typer.Option(..., help="OAuth authorization endpoint URL")
94
- ],
95
- token_endpoint: Annotated[str, typer.Option(..., help="OAuth token endpoint URL")],
96
- scopes: Annotated[
97
- Optional[str], typer.Option(help="Comma-separated OAuth scopes")
168
+ Optional[str],
169
+ typer.Option(help="Fetch a secret delegated to this participant name"),
98
170
  ] = None,
99
- client_secret: Annotated[
100
- Optional[str], typer.Option(help="OAuth client secret (if required)")
101
- ],
102
- redirect_uri: Annotated[
103
- Optional[str], typer.Option(help="Redirect URI for the OAuth flow")
104
- ],
105
- pkce: Annotated[bool, typer.Option(help="Use PKCE (recommended)")] = True,
171
+ out: Annotated[
172
+ str,
173
+ typer.Option(
174
+ "--out",
175
+ "-o",
176
+ help="Output file path, or '-' for stdout",
177
+ ),
178
+ ] = "-",
106
179
  ):
107
- """
108
- Run an OAuth2 request test between two participants in the same room.
109
- One will act as the consumer, the other as the provider.
110
- """
180
+ """Get a stored secret by secret id."""
111
181
 
112
182
  account_client = await get_client()
113
183
  try:
114
184
  project_id = await resolve_project_id(project_id=project_id)
185
+ jwt_consumer = await account_client.connect_room(
186
+ project_id=project_id, room=room
187
+ )
188
+
189
+ async with RoomClient(
190
+ protocol=WebSocketClientProtocol(
191
+ url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
192
+ token=jwt_consumer.jwt,
193
+ )
194
+ ) as consumer:
195
+ resp = await consumer.secrets.get_secret(
196
+ secret_id=secret_id,
197
+ delegated_to=delegated_to,
198
+ )
199
+
200
+ if resp is None:
201
+ typer.echo("Secret not found", err=True)
202
+ raise typer.Exit(1)
203
+
204
+ typer.echo(
205
+ f"Got secret name={resp.name} mime_type={resp.mime_type} bytes={len(resp.data)}",
206
+ err=True,
207
+ )
208
+ _write_bytes(output_path=out, data=resp.data)
209
+ if out != "-":
210
+ typer.echo(f"Wrote {len(resp.data)} bytes to {out}", err=True)
211
+
212
+ finally:
213
+ await account_client.close()
115
214
 
215
+
216
+ @app.async_command("set")
217
+ async def secret_set(
218
+ *,
219
+ project_id: ProjectIdOption,
220
+ room: RoomOption,
221
+ secret_id: Annotated[str, typer.Option(..., help="Secret ID")],
222
+ mime_type: Annotated[
223
+ Optional[str],
224
+ typer.Option("--type", help="Secret MIME type"),
225
+ ] = None,
226
+ name: Annotated[
227
+ Optional[str],
228
+ typer.Option(help="Optional secret name"),
229
+ ] = None,
230
+ delegated_to: Annotated[
231
+ Optional[str],
232
+ typer.Option(help="Store a secret delegated to this participant name"),
233
+ ] = None,
234
+ input_path: Annotated[
235
+ str,
236
+ typer.Option(
237
+ "--in",
238
+ "-i",
239
+ help="Input file path, or '-' for stdin",
240
+ ),
241
+ ] = "-",
242
+ ):
243
+ """Set/store a secret (bytes from stdin or file)."""
244
+
245
+ secret_bytes = _read_bytes(input_path=input_path)
246
+
247
+ account_client = await get_client()
248
+ try:
249
+ project_id = await resolve_project_id(project_id=project_id)
116
250
  jwt_consumer = await account_client.connect_room(
117
251
  project_id=project_id, room=room
118
252
  )
@@ -123,20 +257,15 @@ async def get(
123
257
  token=jwt_consumer.jwt,
124
258
  )
125
259
  ) as consumer:
126
- print("[green]Requesting OAuth token from consumer side...[/green]")
127
- token = await consumer.secrets.get_offline_oauth_token(
128
- oauth=OAuthClientConfig(
129
- client_id=client_id,
130
- authorization_endpoint=authorization_endpoint,
131
- token_endpoint=token_endpoint,
132
- scopes=scopes.split(",") if scopes is not None else scopes,
133
- client_secret=client_secret,
134
- no_pkce=not pkce,
135
- ),
260
+ await consumer.secrets.set_secret(
261
+ secret_id=secret_id,
262
+ type=mime_type,
263
+ name=name,
136
264
  delegated_to=delegated_to,
265
+ data=secret_bytes,
137
266
  )
138
267
 
139
- print(f"[bold cyan]Got access token:[/bold cyan] {token}")
268
+ typer.echo(f"Stored {len(secret_bytes)} bytes", err=True)
140
269
 
141
270
  finally:
142
271
  await account_client.close()
@@ -166,7 +295,7 @@ async def list(
166
295
  token=jwt_consumer.jwt,
167
296
  )
168
297
  ) as consumer:
169
- secrets = await consumer.secrets.list_user_secrets()
298
+ secrets = await consumer.secrets.list_secrets()
170
299
  output = []
171
300
  for s in secrets:
172
301
  output.append(s.model_dump(mode="json"))
@@ -205,7 +334,7 @@ async def delete(
205
334
  token=jwt_consumer.jwt,
206
335
  )
207
336
  ) as consumer:
208
- await consumer.secrets.delete_user_secret(id=id, delegated_to=delegated_to)
337
+ await consumer.secrets.delete_secret(id=id, delegated_to=delegated_to)
209
338
  print("deleted secret")
210
339
 
211
340
  finally:
meshagent/cli/room.py CHANGED
@@ -5,13 +5,16 @@ from meshagent.cli import agent
5
5
  from meshagent.cli import messaging
6
6
  from meshagent.cli import storage
7
7
  from meshagent.cli import developer
8
- from meshagent.cli import cli_secrets
8
+ from meshagent.cli import oauth2
9
9
  from meshagent.cli import containers
10
10
 
11
+ from meshagent.cli import sync
12
+
13
+
11
14
  app = async_typer.AsyncTyper(help="Operate within a room")
12
15
 
13
16
  app.add_typer(agent.app, name="agents", help="Interact with agents and toolkits")
14
- app.add_typer(cli_secrets.app, name="secret", help="Manage secrets for your project")
17
+ app.add_typer(oauth2.app, name="secrets", help="Manage secrets for your project")
15
18
  app.add_typer(queue.app, name="queue", help="Use queues in a room")
16
19
  app.add_typer(messaging.app, name="messaging", help="Send and receive messages")
17
20
  app.add_typer(storage.app, name="storage", help="Manage storage for a room")
@@ -20,3 +23,4 @@ app.add_typer(database.app, name="database", help="Manage database tables in a r
20
23
  app.add_typer(
21
24
  containers.app, name="container", help="Manage containers and images in a room"
22
25
  )
26
+ app.add_typer(sync.app, name="sync")