meshagent-cli 0.22.2__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.

Potentially problematic release.


This version of meshagent-cli might be problematic. Click here for more details.

Files changed (45) hide show
  1. meshagent/cli/__init__.py +3 -0
  2. meshagent/cli/agent.py +273 -0
  3. meshagent/cli/api_keys.py +102 -0
  4. meshagent/cli/async_typer.py +79 -0
  5. meshagent/cli/auth.py +30 -0
  6. meshagent/cli/auth_async.py +295 -0
  7. meshagent/cli/call.py +215 -0
  8. meshagent/cli/chatbot.py +1983 -0
  9. meshagent/cli/cli.py +187 -0
  10. meshagent/cli/cli_mcp.py +408 -0
  11. meshagent/cli/cli_secrets.py +414 -0
  12. meshagent/cli/common_options.py +47 -0
  13. meshagent/cli/containers.py +725 -0
  14. meshagent/cli/database.py +997 -0
  15. meshagent/cli/developer.py +70 -0
  16. meshagent/cli/exec.py +397 -0
  17. meshagent/cli/helper.py +236 -0
  18. meshagent/cli/helpers.py +185 -0
  19. meshagent/cli/host.py +41 -0
  20. meshagent/cli/mailbot.py +1295 -0
  21. meshagent/cli/mailboxes.py +223 -0
  22. meshagent/cli/meeting_transcriber.py +138 -0
  23. meshagent/cli/messaging.py +157 -0
  24. meshagent/cli/multi.py +357 -0
  25. meshagent/cli/oauth2.py +341 -0
  26. meshagent/cli/participant_token.py +63 -0
  27. meshagent/cli/port.py +70 -0
  28. meshagent/cli/projects.py +105 -0
  29. meshagent/cli/queue.py +91 -0
  30. meshagent/cli/room.py +26 -0
  31. meshagent/cli/rooms.py +214 -0
  32. meshagent/cli/services.py +722 -0
  33. meshagent/cli/sessions.py +26 -0
  34. meshagent/cli/storage.py +813 -0
  35. meshagent/cli/sync.py +434 -0
  36. meshagent/cli/task_runner.py +1317 -0
  37. meshagent/cli/version.py +1 -0
  38. meshagent/cli/voicebot.py +624 -0
  39. meshagent/cli/webhook.py +100 -0
  40. meshagent/cli/worker.py +1403 -0
  41. meshagent_cli-0.22.2.dist-info/METADATA +49 -0
  42. meshagent_cli-0.22.2.dist-info/RECORD +45 -0
  43. meshagent_cli-0.22.2.dist-info/WHEEL +5 -0
  44. meshagent_cli-0.22.2.dist-info/entry_points.txt +2 -0
  45. meshagent_cli-0.22.2.dist-info/top_level.txt +1 -0
meshagent/cli/multi.py ADDED
@@ -0,0 +1,357 @@
1
+ import typer
2
+ from meshagent.cli import async_typer
3
+ from meshagent.cli.host import run_services, set_deferred, service_specs, get_service
4
+ from meshagent.cli.common_options import ProjectIdOption
5
+ from typing import Annotated, Optional
6
+
7
+ import importlib.util
8
+ from pathlib import Path
9
+ import click
10
+ import shlex
11
+
12
+ from rich import print
13
+
14
+ from meshagent.agents import Agent
15
+
16
+ from typer.main import get_command
17
+
18
+ from meshagent.cli.common_options import RoomOption
19
+ from meshagent.cli.helper import (
20
+ get_client,
21
+ resolve_project_id,
22
+ )
23
+ from aiohttp import ClientResponseError
24
+ import asyncio
25
+
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
30
+
31
+ from meshagent.cli.chatbot import service as chatbot_service
32
+ from meshagent.cli.worker import service as worker_service
33
+ from meshagent.cli.mailbot import service as mailbot_service
34
+ from meshagent.cli.voicebot import service as voicebot_service
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
+
41
+ import yaml
42
+
43
+
44
+ app = async_typer.AsyncTyper(help="Connect agents and tools to a room")
45
+
46
+ cli_service = async_typer.AsyncTyper(help="Add agents to a team")
47
+
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)
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)
58
+
59
+
60
+ @cli_service.async_command("python")
61
+ async def python(
62
+ *,
63
+ module: str,
64
+ host: Annotated[
65
+ Optional[str], typer.Option(help="Host to bind the service on")
66
+ ] = None,
67
+ port: Annotated[
68
+ Optional[int], typer.Option(help="Port to bind the service on")
69
+ ] = None,
70
+ path: Annotated[
71
+ Optional[str],
72
+ typer.Option(help="A path to add the service at"),
73
+ ] = None,
74
+ identity: Annotated[
75
+ Optional[str],
76
+ typer.Option(help="The desired identity for the service"),
77
+ ] = None,
78
+ name: Annotated[
79
+ str, typer.Option(help="Entry-point name in the Python module")
80
+ ] = "main",
81
+ ):
82
+ service = get_service(host=host, port=port)
83
+
84
+ if path is None:
85
+ path = "/agent"
86
+ i = 0
87
+ while service.has_path(path):
88
+ i += 1
89
+ path = f"/agent{i}"
90
+
91
+ module = import_from_path(module)
92
+ service.add_path(path=path, identity=identity, cls=getattr(module, name or "main"))
93
+
94
+
95
+ def execute_via_root(app, line: str, *, prog_name="meshagent") -> int:
96
+ cmd = get_command(app)
97
+ try:
98
+ cmd.main(args=shlex.split(line), prog_name=prog_name, standalone_mode=False)
99
+ return 0
100
+ except click.ClickException as e:
101
+ e.show()
102
+ return e.exit_code
103
+
104
+
105
+ subcommand_help = """a list of sub commands to run, seperated by semicolons
106
+
107
+ available sub commands:
108
+
109
+ chatbot ...;
110
+ mailbot ...;
111
+ worker ...;
112
+ voicebot ...;
113
+ python path-to-python-file.py --name=NameOfModule;
114
+
115
+ chatbot, worker, and mailbot command arguments mirror those of the respective meshagent chatbot service, meshagent mailbot service, meshagent voicebot service, and meshagent worker service commands.
116
+ """
117
+
118
+
119
+ def build_spec(
120
+ *,
121
+ command: Annotated[str, typer.Option("-c", help=subcommand_help)],
122
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
123
+ service_description: Annotated[
124
+ Optional[str], typer.Option("--service-description", help="service description")
125
+ ] = None,
126
+ service_title: Annotated[
127
+ Optional[str],
128
+ typer.Option("--service-title", help="a display name for the service"),
129
+ ] = None,
130
+ ):
131
+ for c in command.split(";"):
132
+ if execute_via_root(cli_service, c, prog_name="meshagent") != 0:
133
+ print(f"[red]{c} failed[/red]")
134
+ raise typer.Exit(1)
135
+
136
+ specs = service_specs()
137
+ if len(specs) == 0:
138
+ print("[red]found no services, specify at least one agent or tool to run[/red]")
139
+ raise typer.Exit(1)
140
+
141
+ if len(specs) > 1:
142
+ print(
143
+ "[red]found multiple services leave host and port empty or use the same port for each command[/red]"
144
+ )
145
+ raise typer.Exit(1)
146
+
147
+ spec = specs[0]
148
+ spec.metadata.annotations = {
149
+ "meshagent.service.id": service_name,
150
+ }
151
+ for port in spec.ports:
152
+ port.num = "*"
153
+
154
+ spec.metadata.name = service_name
155
+ spec.metadata.description = service_description
156
+ spec.container.image = (
157
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
158
+ )
159
+ spec.container.command = (
160
+ f'meshagent multi service -c "{command.replace('"', '\\"')}"'
161
+ )
162
+
163
+
164
+ @app.async_command("spec")
165
+ async def spec(
166
+ command: Annotated[str, typer.Option("-c", help=subcommand_help)],
167
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
168
+ service_description: Annotated[
169
+ Optional[str], typer.Option("--service-description", help="service description")
170
+ ] = None,
171
+ service_title: Annotated[
172
+ Optional[str],
173
+ typer.Option("--service-title", help="a display name for the service"),
174
+ ] = None,
175
+ ):
176
+ set_deferred(True)
177
+
178
+ spec = build_spec(
179
+ command=command,
180
+ service_name=service_name,
181
+ service_description=service_description,
182
+ service_title=service_title,
183
+ )
184
+
185
+ print(yaml.dump(spec.model_dump(mode="json", exclude_none=True), sort_keys=False))
186
+
187
+
188
+ @app.async_command("deploy")
189
+ async def deploy(
190
+ project_id: ProjectIdOption,
191
+ command: Annotated[str, typer.Option("-c", help=subcommand_help)],
192
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
193
+ service_description: Annotated[
194
+ Optional[str], typer.Option("--service-description", help="service description")
195
+ ] = None,
196
+ service_title: Annotated[
197
+ Optional[str],
198
+ typer.Option("--service-title", help="a display name for the service"),
199
+ ] = None,
200
+ room: Annotated[
201
+ Optional[str],
202
+ typer.Option("--room", help="The name of a room to create the service for"),
203
+ ] = None,
204
+ ):
205
+ project_id = await resolve_project_id(project_id)
206
+
207
+ client = await get_client()
208
+ try:
209
+ set_deferred(True)
210
+
211
+ spec = build_spec(
212
+ command=command,
213
+ service_name=service_name,
214
+ service_description=service_description,
215
+ service_title=service_title,
216
+ )
217
+
218
+ spec.container.secrets = []
219
+
220
+ id = None
221
+ try:
222
+ if id is None:
223
+ if room is None:
224
+ services = await client.list_services(project_id=project_id)
225
+ else:
226
+ services = await client.list_room_services(
227
+ project_id=project_id, room_name=room
228
+ )
229
+
230
+ for s in services:
231
+ if s.metadata.name == spec.metadata.name:
232
+ id = s.id
233
+
234
+ if id is None:
235
+ if room is None:
236
+ id = await client.create_service(
237
+ project_id=project_id, service=spec
238
+ )
239
+ else:
240
+ id = await client.create_room_service(
241
+ project_id=project_id, service=spec, room_name=room
242
+ )
243
+
244
+ else:
245
+ spec.id = id
246
+ if room is None:
247
+ await client.update_service(
248
+ project_id=project_id, service_id=id, service=spec
249
+ )
250
+ else:
251
+ await client.update_room_service(
252
+ project_id=project_id,
253
+ service_id=id,
254
+ service=spec,
255
+ room_name=room,
256
+ )
257
+
258
+ except ClientResponseError as exc:
259
+ if exc.status == 409:
260
+ print(f"[red]Service name already in use: {spec.metadata.name}[/red]")
261
+ raise typer.Exit(code=1)
262
+ raise
263
+ else:
264
+ print(f"[green]Deployed service:[/] {id}")
265
+
266
+ finally:
267
+ await client.close()
268
+
269
+
270
+ @app.async_command("service")
271
+ async def host(
272
+ host: Annotated[
273
+ Optional[str], typer.Option(help="Host to bind the service on")
274
+ ] = None,
275
+ port: Annotated[
276
+ Optional[int], typer.Option(help="Port to bind the service on")
277
+ ] = None,
278
+ command: Annotated[str, typer.Option("-c", help=subcommand_help)] = [],
279
+ ):
280
+ set_deferred(True)
281
+
282
+ for c in command.split(";"):
283
+ if execute_via_root(cli_service, c, prog_name="meshagent") != 0:
284
+ print(f"[red]{c} failed[/red]")
285
+ raise typer.Exit(1)
286
+
287
+ await run_services()
288
+
289
+
290
+ def import_from_path(path: str, module_name: str | None = None):
291
+ path = Path(path)
292
+ module_name = module_name or path.stem
293
+
294
+ spec = importlib.util.spec_from_file_location(module_name, path)
295
+ if spec is None or spec.loader is None:
296
+ raise ImportError(f"Cannot load spec for {path}")
297
+
298
+ module = importlib.util.module_from_spec(spec)
299
+ spec.loader.exec_module(module)
300
+ return module
301
+
302
+
303
+ @app.async_command("join")
304
+ async def join(
305
+ *,
306
+ project_id: ProjectIdOption,
307
+ command: Annotated[str, typer.Option("-c", help=subcommand_help)] = [],
308
+ port: Annotated[
309
+ int,
310
+ typer.Option(
311
+ "--port",
312
+ "-p",
313
+ help=(
314
+ "a port number to run the agent on (will set MESHAGENT_PORT environment variable when launching the service)"
315
+ ),
316
+ ),
317
+ ] = None,
318
+ room: RoomOption,
319
+ key: Annotated[
320
+ str,
321
+ typer.Option("--key", help="an api key to sign the token with"),
322
+ ] = None,
323
+ ):
324
+ set_deferred(True)
325
+
326
+ if room is None:
327
+ print("[bold red]--room is required[/bold red]")
328
+ raise typer.Exit(-1)
329
+
330
+ for c in command.split(";"):
331
+ execute_via_root(cli_join, c + f" --room={room}", prog_name="meshagent")
332
+
333
+ from meshagent.cli.host import agents
334
+
335
+ try:
336
+
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
+ )
355
+
356
+ except KeyboardInterrupt:
357
+ pass
@@ -0,0 +1,341 @@
1
+ from meshagent.cli import async_typer
2
+ from meshagent.cli.common_options import ProjectIdOption, RoomOption
3
+ from meshagent.cli.helper import (
4
+ get_client,
5
+ resolve_project_id,
6
+ )
7
+ from meshagent.api.oauth import OAuthClientConfig
8
+ from meshagent.api import RoomClient, WebSocketClientProtocol
9
+ from meshagent.api.helpers import meshagent_base_url, websocket_room_url
10
+ from rich import print
11
+ from typing import Annotated, Optional
12
+ from pathlib import Path
13
+ import typer
14
+ import json
15
+ import sys
16
+
17
+ app = async_typer.AsyncTyper(help="OAuth2 test commands")
18
+
19
+
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")
35
+ async def oauth2(
36
+ *,
37
+ project_id: ProjectIdOption,
38
+ room: RoomOption,
39
+ from_participant_id: Annotated[
40
+ str,
41
+ typer.Option(..., help="Participant ID to request the token from"),
42
+ ],
43
+ client_id: Annotated[str, typer.Option(..., help="OAuth client ID")],
44
+ authorization_endpoint: Annotated[
45
+ str, typer.Option(..., help="OAuth authorization endpoint URL")
46
+ ],
47
+ token_endpoint: Annotated[str, typer.Option(..., help="OAuth token endpoint URL")],
48
+ scopes: Annotated[
49
+ Optional[str], typer.Option(help="Comma-separated OAuth scopes")
50
+ ] = None,
51
+ client_secret: Annotated[
52
+ Optional[str], typer.Option(help="OAuth client secret (if required)")
53
+ ],
54
+ redirect_uri: Annotated[
55
+ Optional[str], typer.Option(help="Redirect URI for the OAuth flow")
56
+ ],
57
+ pkce: Annotated[bool, typer.Option(help="Use PKCE (recommended)")] = True,
58
+ ):
59
+ """
60
+ Run an OAuth2 request test between two participants in the same room.
61
+ One will act as the consumer, the other as the provider.
62
+ """
63
+
64
+ account_client = await get_client()
65
+ try:
66
+ project_id = await resolve_project_id(project_id=project_id)
67
+
68
+ jwt_consumer = await account_client.connect_room(
69
+ project_id=project_id, room=room
70
+ )
71
+
72
+ async with RoomClient(
73
+ protocol=WebSocketClientProtocol(
74
+ url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
75
+ token=jwt_consumer.jwt,
76
+ )
77
+ ) as consumer:
78
+ print("[green]Requesting OAuth token from consumer side...[/green]")
79
+ token = await consumer.secrets.request_oauth_token(
80
+ oauth=OAuthClientConfig(
81
+ client_id=client_id,
82
+ authorization_endpoint=authorization_endpoint,
83
+ token_endpoint=token_endpoint,
84
+ scopes=scopes.split(",") if scopes is not None else scopes,
85
+ client_secret=client_secret,
86
+ no_pkce=not pkce,
87
+ ),
88
+ from_participant_id=from_participant_id,
89
+ timeout=300,
90
+ redirect_uri=redirect_uri,
91
+ )
92
+
93
+ print(f"[bold cyan]Got access token:[/bold cyan] {token}")
94
+
95
+ finally:
96
+ await account_client.close()
97
+
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
+
161
+ @app.async_command("get")
162
+ async def secret_get(
163
+ *,
164
+ project_id: ProjectIdOption,
165
+ room: RoomOption,
166
+ secret_id: Annotated[str, typer.Option(..., help="Secret ID")],
167
+ delegated_to: Annotated[
168
+ Optional[str],
169
+ typer.Option(help="Fetch a secret delegated to this participant name"),
170
+ ] = None,
171
+ out: Annotated[
172
+ str,
173
+ typer.Option(
174
+ "--out",
175
+ "-o",
176
+ help="Output file path, or '-' for stdout",
177
+ ),
178
+ ] = "-",
179
+ ):
180
+ """Get a stored secret by secret id."""
181
+
182
+ account_client = await get_client()
183
+ try:
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()
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)
250
+ jwt_consumer = await account_client.connect_room(
251
+ project_id=project_id, room=room
252
+ )
253
+
254
+ async with RoomClient(
255
+ protocol=WebSocketClientProtocol(
256
+ url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
257
+ token=jwt_consumer.jwt,
258
+ )
259
+ ) as consumer:
260
+ await consumer.secrets.set_secret(
261
+ secret_id=secret_id,
262
+ type=mime_type,
263
+ name=name,
264
+ delegated_to=delegated_to,
265
+ data=secret_bytes,
266
+ )
267
+
268
+ typer.echo(f"Stored {len(secret_bytes)} bytes", err=True)
269
+
270
+ finally:
271
+ await account_client.close()
272
+
273
+
274
+ @app.async_command("list")
275
+ async def list(
276
+ *,
277
+ project_id: ProjectIdOption,
278
+ room: RoomOption,
279
+ ):
280
+ """
281
+ list secrets
282
+ """
283
+
284
+ account_client = await get_client()
285
+ try:
286
+ project_id = await resolve_project_id(project_id=project_id)
287
+
288
+ jwt_consumer = await account_client.connect_room(
289
+ project_id=project_id, room=room
290
+ )
291
+
292
+ async with RoomClient(
293
+ protocol=WebSocketClientProtocol(
294
+ url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
295
+ token=jwt_consumer.jwt,
296
+ )
297
+ ) as consumer:
298
+ secrets = await consumer.secrets.list_secrets()
299
+ output = []
300
+ for s in secrets:
301
+ output.append(s.model_dump(mode="json"))
302
+
303
+ print(json.dumps(output, indent=2))
304
+
305
+ finally:
306
+ await account_client.close()
307
+
308
+
309
+ @app.async_command("delete")
310
+ async def delete(
311
+ *,
312
+ project_id: ProjectIdOption,
313
+ room: RoomOption,
314
+ id: str,
315
+ delegated_to: Annotated[
316
+ str, typer.Option(help="The value of the delegated_to field of the secret")
317
+ ],
318
+ ):
319
+ """
320
+ delete a secret
321
+ """
322
+
323
+ account_client = await get_client()
324
+ try:
325
+ project_id = await resolve_project_id(project_id=project_id)
326
+
327
+ jwt_consumer = await account_client.connect_room(
328
+ project_id=project_id, room=room
329
+ )
330
+
331
+ async with RoomClient(
332
+ protocol=WebSocketClientProtocol(
333
+ url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
334
+ token=jwt_consumer.jwt,
335
+ )
336
+ ) as consumer:
337
+ await consumer.secrets.delete_secret(id=id, delegated_to=delegated_to)
338
+ print("deleted secret")
339
+
340
+ finally:
341
+ await account_client.close()