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.
- meshagent/cli/agent.py +23 -13
- meshagent/cli/api_keys.py +4 -4
- meshagent/cli/async_typer.py +52 -4
- meshagent/cli/call.py +27 -36
- meshagent/cli/chatbot.py +1559 -177
- meshagent/cli/cli.py +23 -22
- meshagent/cli/cli_mcp.py +92 -28
- meshagent/cli/cli_secrets.py +10 -10
- meshagent/cli/common_options.py +19 -4
- meshagent/cli/containers.py +164 -16
- meshagent/cli/database.py +997 -0
- meshagent/cli/developer.py +3 -3
- meshagent/cli/exec.py +22 -6
- meshagent/cli/helper.py +101 -12
- meshagent/cli/helpers.py +65 -11
- meshagent/cli/host.py +41 -0
- meshagent/cli/mailbot.py +1104 -79
- meshagent/cli/mailboxes.py +223 -0
- meshagent/cli/meeting_transcriber.py +29 -15
- meshagent/cli/messaging.py +7 -10
- meshagent/cli/multi.py +357 -0
- meshagent/cli/oauth2.py +192 -40
- meshagent/cli/participant_token.py +5 -3
- meshagent/cli/port.py +70 -0
- meshagent/cli/queue.py +2 -2
- meshagent/cli/room.py +24 -212
- meshagent/cli/rooms.py +214 -0
- meshagent/cli/services.py +269 -37
- meshagent/cli/sessions.py +5 -5
- meshagent/cli/storage.py +5 -5
- meshagent/cli/sync.py +434 -0
- meshagent/cli/task_runner.py +1317 -0
- meshagent/cli/version.py +1 -1
- meshagent/cli/voicebot.py +544 -98
- meshagent/cli/webhook.py +7 -7
- meshagent/cli/worker.py +1403 -0
- {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/METADATA +15 -13
- meshagent_cli-0.23.0.dist-info/RECORD +45 -0
- {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/WHEEL +1 -1
- meshagent_cli-0.7.0.dist-info/RECORD +0 -36
- {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/entry_points.txt +0 -0
- {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/top_level.txt +0 -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
|
meshagent/cli/oauth2.py
CHANGED
|
@@ -9,25 +9,52 @@ 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
|
-
|
|
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
|
-
project_id: ProjectIdOption
|
|
37
|
+
project_id: ProjectIdOption,
|
|
22
38
|
room: RoomOption,
|
|
23
|
-
from_participant_id: Annotated[
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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,
|
|
31
58
|
):
|
|
32
59
|
"""
|
|
33
60
|
Run an OAuth2 request test between two participants in the same room.
|
|
@@ -69,29 +96,157 @@ async def oauth2(
|
|
|
69
96
|
await account_client.close()
|
|
70
97
|
|
|
71
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
|
+
|
|
72
161
|
@app.async_command("get")
|
|
73
|
-
async def
|
|
162
|
+
async def secret_get(
|
|
74
163
|
*,
|
|
75
|
-
project_id: ProjectIdOption
|
|
164
|
+
project_id: ProjectIdOption,
|
|
76
165
|
room: RoomOption,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
] = "-",
|
|
85
179
|
):
|
|
86
|
-
"""
|
|
87
|
-
Run an OAuth2 request test between two participants in the same room.
|
|
88
|
-
One will act as the consumer, the other as the provider.
|
|
89
|
-
"""
|
|
180
|
+
"""Get a stored secret by secret id."""
|
|
90
181
|
|
|
91
182
|
account_client = await get_client()
|
|
92
183
|
try:
|
|
93
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
|
+
|
|
94
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)
|
|
95
250
|
jwt_consumer = await account_client.connect_room(
|
|
96
251
|
project_id=project_id, room=room
|
|
97
252
|
)
|
|
@@ -102,20 +257,15 @@ async def get(
|
|
|
102
257
|
token=jwt_consumer.jwt,
|
|
103
258
|
)
|
|
104
259
|
) as consumer:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
authorization_endpoint=authorization_endpoint,
|
|
110
|
-
token_endpoint=token_endpoint,
|
|
111
|
-
scopes=scopes.split(",") if scopes is not None else scopes,
|
|
112
|
-
client_secret=client_secret,
|
|
113
|
-
no_pkce=not pkce,
|
|
114
|
-
),
|
|
260
|
+
await consumer.secrets.set_secret(
|
|
261
|
+
secret_id=secret_id,
|
|
262
|
+
type=mime_type,
|
|
263
|
+
name=name,
|
|
115
264
|
delegated_to=delegated_to,
|
|
265
|
+
data=secret_bytes,
|
|
116
266
|
)
|
|
117
267
|
|
|
118
|
-
|
|
268
|
+
typer.echo(f"Stored {len(secret_bytes)} bytes", err=True)
|
|
119
269
|
|
|
120
270
|
finally:
|
|
121
271
|
await account_client.close()
|
|
@@ -124,7 +274,7 @@ async def get(
|
|
|
124
274
|
@app.async_command("list")
|
|
125
275
|
async def list(
|
|
126
276
|
*,
|
|
127
|
-
project_id: ProjectIdOption
|
|
277
|
+
project_id: ProjectIdOption,
|
|
128
278
|
room: RoomOption,
|
|
129
279
|
):
|
|
130
280
|
"""
|
|
@@ -145,7 +295,7 @@ async def list(
|
|
|
145
295
|
token=jwt_consumer.jwt,
|
|
146
296
|
)
|
|
147
297
|
) as consumer:
|
|
148
|
-
secrets = await consumer.secrets.
|
|
298
|
+
secrets = await consumer.secrets.list_secrets()
|
|
149
299
|
output = []
|
|
150
300
|
for s in secrets:
|
|
151
301
|
output.append(s.model_dump(mode="json"))
|
|
@@ -159,10 +309,12 @@ async def list(
|
|
|
159
309
|
@app.async_command("delete")
|
|
160
310
|
async def delete(
|
|
161
311
|
*,
|
|
162
|
-
project_id: ProjectIdOption
|
|
312
|
+
project_id: ProjectIdOption,
|
|
163
313
|
room: RoomOption,
|
|
164
314
|
id: str,
|
|
165
|
-
delegated_to: Annotated[
|
|
315
|
+
delegated_to: Annotated[
|
|
316
|
+
str, typer.Option(help="The value of the delegated_to field of the secret")
|
|
317
|
+
],
|
|
166
318
|
):
|
|
167
319
|
"""
|
|
168
320
|
delete a secret
|
|
@@ -182,7 +334,7 @@ async def delete(
|
|
|
182
334
|
token=jwt_consumer.jwt,
|
|
183
335
|
)
|
|
184
336
|
) as consumer:
|
|
185
|
-
await consumer.secrets.
|
|
337
|
+
await consumer.secrets.delete_secret(id=id, delegated_to=delegated_to)
|
|
186
338
|
print("deleted secret")
|
|
187
339
|
|
|
188
340
|
finally:
|
|
@@ -10,13 +10,13 @@ from meshagent.api.participant_token import ParticipantTokenSpec
|
|
|
10
10
|
from pydantic_yaml import parse_yaml_raw_as
|
|
11
11
|
from meshagent.cli.common_options import ProjectIdOption
|
|
12
12
|
|
|
13
|
-
app = async_typer.AsyncTyper()
|
|
13
|
+
app = async_typer.AsyncTyper(help="Generate participant tokens (JWTs)")
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@app.async_command("generate")
|
|
16
|
+
@app.async_command("generate", help="Generate a participant token (JWT) from a spec")
|
|
17
17
|
async def generate(
|
|
18
18
|
*,
|
|
19
|
-
project_id: ProjectIdOption
|
|
19
|
+
project_id: ProjectIdOption,
|
|
20
20
|
output: Annotated[
|
|
21
21
|
Optional[str],
|
|
22
22
|
typer.Option("--output", "-o", help="File path to a file"),
|
|
@@ -30,6 +30,8 @@ async def generate(
|
|
|
30
30
|
typer.Option("--key", help="an api key to sign the token with"),
|
|
31
31
|
] = None,
|
|
32
32
|
):
|
|
33
|
+
"""Generate a signed participant token (JWT) from a YAML spec."""
|
|
34
|
+
|
|
33
35
|
project_id = await resolve_project_id(project_id=project_id)
|
|
34
36
|
key = await resolve_key(project_id=project_id, key=key)
|
|
35
37
|
|