meshagent-cli 0.7.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/__init__.py +3 -0
- meshagent/cli/agent.py +263 -0
- meshagent/cli/api_keys.py +102 -0
- meshagent/cli/async_typer.py +31 -0
- meshagent/cli/auth.py +30 -0
- meshagent/cli/auth_async.py +295 -0
- meshagent/cli/call.py +224 -0
- meshagent/cli/chatbot.py +601 -0
- meshagent/cli/cli.py +186 -0
- meshagent/cli/cli_mcp.py +344 -0
- meshagent/cli/cli_secrets.py +414 -0
- meshagent/cli/common_options.py +32 -0
- meshagent/cli/containers.py +577 -0
- meshagent/cli/developer.py +70 -0
- meshagent/cli/exec.py +381 -0
- meshagent/cli/helper.py +147 -0
- meshagent/cli/helpers.py +131 -0
- meshagent/cli/mailbot.py +270 -0
- meshagent/cli/meeting_transcriber.py +124 -0
- meshagent/cli/messaging.py +160 -0
- meshagent/cli/oauth2.py +189 -0
- meshagent/cli/participant_token.py +61 -0
- meshagent/cli/projects.py +105 -0
- meshagent/cli/queue.py +91 -0
- meshagent/cli/room.py +214 -0
- meshagent/cli/services.py +490 -0
- meshagent/cli/sessions.py +26 -0
- meshagent/cli/storage.py +813 -0
- meshagent/cli/version.py +1 -0
- meshagent/cli/voicebot.py +178 -0
- meshagent/cli/webhook.py +100 -0
- meshagent_cli-0.7.0.dist-info/METADATA +47 -0
- meshagent_cli-0.7.0.dist-info/RECORD +36 -0
- meshagent_cli-0.7.0.dist-info/WHEEL +5 -0
- meshagent_cli-0.7.0.dist-info/entry_points.txt +2 -0
- meshagent_cli-0.7.0.dist-info/top_level.txt +1 -0
meshagent/cli/oauth2.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
import typer
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
app = async_typer.AsyncTyper(help="OAuth2 test commands")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.async_command("request")
|
|
19
|
+
async def oauth2(
|
|
20
|
+
*,
|
|
21
|
+
project_id: ProjectIdOption = None,
|
|
22
|
+
room: RoomOption,
|
|
23
|
+
from_participant_id: Annotated[str, typer.Option()],
|
|
24
|
+
client_id: Annotated[str, typer.Option()],
|
|
25
|
+
authorization_endpoint: Annotated[str, typer.Option()],
|
|
26
|
+
token_endpoint: Annotated[str, typer.Option()],
|
|
27
|
+
scopes: Annotated[Optional[str], typer.Option()] = None,
|
|
28
|
+
client_secret: Annotated[Optional[str], typer.Option()],
|
|
29
|
+
redirect_uri: Annotated[Optional[str], typer.Option()],
|
|
30
|
+
pkce: Annotated[bool, typer.Option()] = True,
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Run an OAuth2 request test between two participants in the same room.
|
|
34
|
+
One will act as the consumer, the other as the provider.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
account_client = await get_client()
|
|
38
|
+
try:
|
|
39
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
40
|
+
|
|
41
|
+
jwt_consumer = await account_client.connect_room(
|
|
42
|
+
project_id=project_id, room=room
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async with RoomClient(
|
|
46
|
+
protocol=WebSocketClientProtocol(
|
|
47
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
48
|
+
token=jwt_consumer.jwt,
|
|
49
|
+
)
|
|
50
|
+
) as consumer:
|
|
51
|
+
print("[green]Requesting OAuth token from consumer side...[/green]")
|
|
52
|
+
token = await consumer.secrets.request_oauth_token(
|
|
53
|
+
oauth=OAuthClientConfig(
|
|
54
|
+
client_id=client_id,
|
|
55
|
+
authorization_endpoint=authorization_endpoint,
|
|
56
|
+
token_endpoint=token_endpoint,
|
|
57
|
+
scopes=scopes.split(",") if scopes is not None else scopes,
|
|
58
|
+
client_secret=client_secret,
|
|
59
|
+
no_pkce=not pkce,
|
|
60
|
+
),
|
|
61
|
+
from_participant_id=from_participant_id,
|
|
62
|
+
timeout=300,
|
|
63
|
+
redirect_uri=redirect_uri,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
print(f"[bold cyan]Got access token:[/bold cyan] {token}")
|
|
67
|
+
|
|
68
|
+
finally:
|
|
69
|
+
await account_client.close()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.async_command("get")
|
|
73
|
+
async def get(
|
|
74
|
+
*,
|
|
75
|
+
project_id: ProjectIdOption = None,
|
|
76
|
+
room: RoomOption,
|
|
77
|
+
delegated_to: Annotated[str, typer.Option()],
|
|
78
|
+
client_id: Annotated[str, typer.Option()],
|
|
79
|
+
authorization_endpoint: Annotated[str, typer.Option()],
|
|
80
|
+
token_endpoint: Annotated[str, typer.Option()],
|
|
81
|
+
scopes: Annotated[Optional[str], typer.Option()] = None,
|
|
82
|
+
client_secret: Annotated[Optional[str], typer.Option()],
|
|
83
|
+
redirect_uri: Annotated[Optional[str], typer.Option()],
|
|
84
|
+
pkce: Annotated[bool, typer.Option()] = True,
|
|
85
|
+
):
|
|
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
|
+
"""
|
|
90
|
+
|
|
91
|
+
account_client = await get_client()
|
|
92
|
+
try:
|
|
93
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
94
|
+
|
|
95
|
+
jwt_consumer = await account_client.connect_room(
|
|
96
|
+
project_id=project_id, room=room
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async with RoomClient(
|
|
100
|
+
protocol=WebSocketClientProtocol(
|
|
101
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
102
|
+
token=jwt_consumer.jwt,
|
|
103
|
+
)
|
|
104
|
+
) as consumer:
|
|
105
|
+
print("[green]Requesting OAuth token from consumer side...[/green]")
|
|
106
|
+
token = await consumer.secrets.get_offline_oauth_token(
|
|
107
|
+
oauth=OAuthClientConfig(
|
|
108
|
+
client_id=client_id,
|
|
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
|
+
),
|
|
115
|
+
delegated_to=delegated_to,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
print(f"[bold cyan]Got access token:[/bold cyan] {token}")
|
|
119
|
+
|
|
120
|
+
finally:
|
|
121
|
+
await account_client.close()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.async_command("list")
|
|
125
|
+
async def list(
|
|
126
|
+
*,
|
|
127
|
+
project_id: ProjectIdOption = None,
|
|
128
|
+
room: RoomOption,
|
|
129
|
+
):
|
|
130
|
+
"""
|
|
131
|
+
list secrets
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
account_client = await get_client()
|
|
135
|
+
try:
|
|
136
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
137
|
+
|
|
138
|
+
jwt_consumer = await account_client.connect_room(
|
|
139
|
+
project_id=project_id, room=room
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
async with RoomClient(
|
|
143
|
+
protocol=WebSocketClientProtocol(
|
|
144
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
145
|
+
token=jwt_consumer.jwt,
|
|
146
|
+
)
|
|
147
|
+
) as consumer:
|
|
148
|
+
secrets = await consumer.secrets.list_user_secrets()
|
|
149
|
+
output = []
|
|
150
|
+
for s in secrets:
|
|
151
|
+
output.append(s.model_dump(mode="json"))
|
|
152
|
+
|
|
153
|
+
print(json.dumps(output, indent=2))
|
|
154
|
+
|
|
155
|
+
finally:
|
|
156
|
+
await account_client.close()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@app.async_command("delete")
|
|
160
|
+
async def delete(
|
|
161
|
+
*,
|
|
162
|
+
project_id: ProjectIdOption = None,
|
|
163
|
+
room: RoomOption,
|
|
164
|
+
id: str,
|
|
165
|
+
delegated_to: Annotated[Optional[str], typer.Option()] = None,
|
|
166
|
+
):
|
|
167
|
+
"""
|
|
168
|
+
delete a secret
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
account_client = await get_client()
|
|
172
|
+
try:
|
|
173
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
174
|
+
|
|
175
|
+
jwt_consumer = await account_client.connect_room(
|
|
176
|
+
project_id=project_id, room=room
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
async with RoomClient(
|
|
180
|
+
protocol=WebSocketClientProtocol(
|
|
181
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
182
|
+
token=jwt_consumer.jwt,
|
|
183
|
+
)
|
|
184
|
+
) as consumer:
|
|
185
|
+
await consumer.secrets.delete_user_secret(id=id, delegated_to=delegated_to)
|
|
186
|
+
print("deleted secret")
|
|
187
|
+
|
|
188
|
+
finally:
|
|
189
|
+
await account_client.close()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import print
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
from meshagent.api import ParticipantToken
|
|
5
|
+
from meshagent.cli import async_typer
|
|
6
|
+
from meshagent.cli.helper import get_client, resolve_key, resolve_project_id
|
|
7
|
+
import pathlib
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from meshagent.api.participant_token import ParticipantTokenSpec
|
|
10
|
+
from pydantic_yaml import parse_yaml_raw_as
|
|
11
|
+
from meshagent.cli.common_options import ProjectIdOption
|
|
12
|
+
|
|
13
|
+
app = async_typer.AsyncTyper()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.async_command("generate")
|
|
17
|
+
async def generate(
|
|
18
|
+
*,
|
|
19
|
+
project_id: ProjectIdOption = None,
|
|
20
|
+
output: Annotated[
|
|
21
|
+
Optional[str],
|
|
22
|
+
typer.Option("--output", "-o", help="File path to a file"),
|
|
23
|
+
] = None,
|
|
24
|
+
input: Annotated[
|
|
25
|
+
str,
|
|
26
|
+
typer.Option("--input", "-i", help="File path to a token spec"),
|
|
27
|
+
],
|
|
28
|
+
key: Annotated[
|
|
29
|
+
str,
|
|
30
|
+
typer.Option("--key", help="an api key to sign the token with"),
|
|
31
|
+
] = None,
|
|
32
|
+
):
|
|
33
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
34
|
+
key = await resolve_key(project_id=project_id, key=key)
|
|
35
|
+
|
|
36
|
+
client = await get_client()
|
|
37
|
+
try:
|
|
38
|
+
with open(str(pathlib.Path(input).expanduser().resolve()), "rb") as f:
|
|
39
|
+
spec = parse_yaml_raw_as(ParticipantTokenSpec, f.read())
|
|
40
|
+
|
|
41
|
+
token = ParticipantToken(
|
|
42
|
+
name=spec.identity,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if spec.role is not None:
|
|
46
|
+
token.add_role_grant(role=spec.role)
|
|
47
|
+
if spec.room is not None:
|
|
48
|
+
token.add_room_grant(spec.room)
|
|
49
|
+
|
|
50
|
+
token.add_api_grant(spec.api)
|
|
51
|
+
|
|
52
|
+
if output is None:
|
|
53
|
+
print(token.to_jwt(api_key=key))
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
pathlib.Path(output).expanduser().resolve().write_text(
|
|
57
|
+
token.to_jwt(api_key=key)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
finally:
|
|
61
|
+
await client.close()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import print
|
|
3
|
+
from meshagent.cli import async_typer
|
|
4
|
+
from meshagent.cli.helper import (
|
|
5
|
+
get_client,
|
|
6
|
+
print_json_table,
|
|
7
|
+
set_active_project,
|
|
8
|
+
get_active_project,
|
|
9
|
+
)
|
|
10
|
+
from meshagent.cli.common_options import OutputFormatOption
|
|
11
|
+
|
|
12
|
+
app = async_typer.AsyncTyper(help="Manage or activate your meshagent projects")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.async_command("create")
|
|
16
|
+
async def create(name: str):
|
|
17
|
+
client = await get_client()
|
|
18
|
+
try:
|
|
19
|
+
result = await client.create_project(name)
|
|
20
|
+
print(f"[green]Project created:[/] {result['id']}")
|
|
21
|
+
finally:
|
|
22
|
+
await client.close()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.async_command("list")
|
|
26
|
+
async def list(
|
|
27
|
+
o: OutputFormatOption = "table",
|
|
28
|
+
):
|
|
29
|
+
client = await get_client()
|
|
30
|
+
projects = await client.list_projects()
|
|
31
|
+
active_project = await get_active_project()
|
|
32
|
+
for project in projects["projects"]:
|
|
33
|
+
if project["id"] == active_project:
|
|
34
|
+
project["name"] = "*" + project["name"]
|
|
35
|
+
|
|
36
|
+
if o == "json":
|
|
37
|
+
print(projects)
|
|
38
|
+
else:
|
|
39
|
+
print_json_table(projects["projects"], "id", "name")
|
|
40
|
+
await client.close()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.async_command("activate")
|
|
44
|
+
async def activate(
|
|
45
|
+
project_id: str | None = typer.Argument(None),
|
|
46
|
+
interactive: bool = typer.Option(
|
|
47
|
+
False,
|
|
48
|
+
"-i",
|
|
49
|
+
"--interactive",
|
|
50
|
+
help="Interactively select or create a project",
|
|
51
|
+
),
|
|
52
|
+
):
|
|
53
|
+
client = await get_client()
|
|
54
|
+
try:
|
|
55
|
+
if interactive:
|
|
56
|
+
response = await client.list_projects()
|
|
57
|
+
projects = response["projects"]
|
|
58
|
+
|
|
59
|
+
if not projects:
|
|
60
|
+
if typer.confirm(
|
|
61
|
+
"There are no projects. Would you like to create one?",
|
|
62
|
+
default=True,
|
|
63
|
+
):
|
|
64
|
+
name = typer.prompt("Project name")
|
|
65
|
+
created = await client.create_project(name)
|
|
66
|
+
project_id = created["id"]
|
|
67
|
+
else:
|
|
68
|
+
raise typer.Exit(code=0)
|
|
69
|
+
else:
|
|
70
|
+
for idx, proj in enumerate(projects, start=1):
|
|
71
|
+
print(f"[{idx}] {proj['name']} ({proj['id']})")
|
|
72
|
+
new_project_index = len(projects) + 1
|
|
73
|
+
print(f"[{new_project_index}] Create a new project")
|
|
74
|
+
exit_index = new_project_index + 1
|
|
75
|
+
print(f"[{exit_index}] Exit")
|
|
76
|
+
|
|
77
|
+
choice = typer.prompt("Select a project", type=int)
|
|
78
|
+
if choice == exit_index:
|
|
79
|
+
return
|
|
80
|
+
elif choice == new_project_index:
|
|
81
|
+
name = typer.prompt("Project name")
|
|
82
|
+
# TODO: validate name
|
|
83
|
+
created = await client.create_project(name)
|
|
84
|
+
project_id = created["id"]
|
|
85
|
+
elif 1 <= choice <= len(projects):
|
|
86
|
+
project_id = projects[choice - 1]["id"]
|
|
87
|
+
else:
|
|
88
|
+
print("[red]Invalid selection[/red]")
|
|
89
|
+
raise typer.Exit(code=1)
|
|
90
|
+
|
|
91
|
+
if project_id is None and not interactive:
|
|
92
|
+
print("[red]project_id required[/red]")
|
|
93
|
+
raise typer.Exit(code=1)
|
|
94
|
+
|
|
95
|
+
if project_id is not None:
|
|
96
|
+
projects = (await client.list_projects())["projects"]
|
|
97
|
+
for project in projects:
|
|
98
|
+
if project["id"] == project_id:
|
|
99
|
+
await set_active_project(project_id=project_id)
|
|
100
|
+
return project_id
|
|
101
|
+
|
|
102
|
+
print(f"[red]Invalid project id: {project_id}[/red]")
|
|
103
|
+
raise typer.Exit(code=1)
|
|
104
|
+
finally:
|
|
105
|
+
await client.close()
|
meshagent/cli/queue.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import print
|
|
3
|
+
from typing import Annotated, Optional
|
|
4
|
+
from meshagent.cli.common_options import ProjectIdOption, RoomOption
|
|
5
|
+
import json as _json
|
|
6
|
+
|
|
7
|
+
from meshagent.api.helpers import meshagent_base_url, websocket_room_url
|
|
8
|
+
from meshagent.api import (
|
|
9
|
+
RoomClient,
|
|
10
|
+
WebSocketClientProtocol,
|
|
11
|
+
RoomException,
|
|
12
|
+
)
|
|
13
|
+
from meshagent.cli.helper import resolve_project_id, resolve_room
|
|
14
|
+
from meshagent.cli import async_typer
|
|
15
|
+
from meshagent.cli.helper import get_client
|
|
16
|
+
|
|
17
|
+
app = async_typer.AsyncTyper(help="Use queues in a room")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.async_command("send")
|
|
21
|
+
async def send(
|
|
22
|
+
*,
|
|
23
|
+
project_id: ProjectIdOption = None,
|
|
24
|
+
room: RoomOption,
|
|
25
|
+
queue: Annotated[str, typer.Option(..., help="Queue name")],
|
|
26
|
+
json: Optional[str] = typer.Option(..., help="a JSON message to send to the queue"),
|
|
27
|
+
file: Annotated[
|
|
28
|
+
Optional[str],
|
|
29
|
+
typer.Option("--file", "-f", help="File path to a JSON file"),
|
|
30
|
+
] = None,
|
|
31
|
+
):
|
|
32
|
+
account_client = await get_client()
|
|
33
|
+
try:
|
|
34
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
35
|
+
room = resolve_room(room)
|
|
36
|
+
|
|
37
|
+
connection = await account_client.connect_room(project_id=project_id, room=room)
|
|
38
|
+
|
|
39
|
+
print("[bold green]Connecting to room...[/bold green]")
|
|
40
|
+
async with RoomClient(
|
|
41
|
+
protocol=WebSocketClientProtocol(
|
|
42
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
43
|
+
token=connection.jwt,
|
|
44
|
+
)
|
|
45
|
+
) as client:
|
|
46
|
+
if file is not None:
|
|
47
|
+
with open(file, "rb") as f:
|
|
48
|
+
message = f.read()
|
|
49
|
+
else:
|
|
50
|
+
message = _json.loads(json)
|
|
51
|
+
|
|
52
|
+
await client.queues.send(name=queue, message=message)
|
|
53
|
+
|
|
54
|
+
except RoomException as e:
|
|
55
|
+
print(f"[red]{e}[/red]")
|
|
56
|
+
finally:
|
|
57
|
+
await account_client.close()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.async_command("receive")
|
|
61
|
+
async def receive(
|
|
62
|
+
*,
|
|
63
|
+
project_id: ProjectIdOption = None,
|
|
64
|
+
room: RoomOption,
|
|
65
|
+
queue: Annotated[str, typer.Option(..., help="Queue name")],
|
|
66
|
+
):
|
|
67
|
+
account_client = await get_client()
|
|
68
|
+
try:
|
|
69
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
70
|
+
room = resolve_room(room)
|
|
71
|
+
|
|
72
|
+
connection = await account_client.connect_room(project_id=project_id, room=room)
|
|
73
|
+
|
|
74
|
+
async with RoomClient(
|
|
75
|
+
protocol=WebSocketClientProtocol(
|
|
76
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
77
|
+
token=connection.jwt,
|
|
78
|
+
)
|
|
79
|
+
) as client:
|
|
80
|
+
response = await client.queues.receive(name=queue, wait=False)
|
|
81
|
+
if response is None:
|
|
82
|
+
print("[bold yellow]Queue did not contain any messages.[/bold yellow]")
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
else:
|
|
85
|
+
print(response)
|
|
86
|
+
|
|
87
|
+
except RoomException as e:
|
|
88
|
+
print(f"[red]{e}[/red]")
|
|
89
|
+
raise typer.Exit(1)
|
|
90
|
+
finally:
|
|
91
|
+
await account_client.close()
|
meshagent/cli/room.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# rooms_cli.py (add to the same module or import into your CLI package)
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich import print
|
|
5
|
+
from typing import Annotated, Optional
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from meshagent.cli import async_typer
|
|
9
|
+
from meshagent.cli.common_options import ProjectIdOption
|
|
10
|
+
from meshagent.cli.helper import (
|
|
11
|
+
get_client,
|
|
12
|
+
resolve_project_id,
|
|
13
|
+
resolve_room,
|
|
14
|
+
)
|
|
15
|
+
from meshagent.api import RoomException
|
|
16
|
+
|
|
17
|
+
app = async_typer.AsyncTyper()
|
|
18
|
+
|
|
19
|
+
# ---------------------------
|
|
20
|
+
# Helpers
|
|
21
|
+
# ---------------------------
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def _resolve_room_id_or_fail(
|
|
25
|
+
account_client, *, project_id: str, room_id: Optional[str], room_name: Optional[str]
|
|
26
|
+
) -> str:
|
|
27
|
+
"""
|
|
28
|
+
If room_id is provided, return it.
|
|
29
|
+
Else, resolve via room_name -> account_client.get_room(...).id
|
|
30
|
+
"""
|
|
31
|
+
if room_id:
|
|
32
|
+
return room_id
|
|
33
|
+
if not room_name:
|
|
34
|
+
raise RoomException("You must provide either --id or --name.")
|
|
35
|
+
room = await account_client.get_room(project_id=project_id, name=room_name)
|
|
36
|
+
return room.id
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _maybe_parse_json(label: str, s: Optional[str]):
|
|
40
|
+
if s is None:
|
|
41
|
+
return None
|
|
42
|
+
try:
|
|
43
|
+
return json.loads(s)
|
|
44
|
+
except json.JSONDecodeError as e:
|
|
45
|
+
raise RoomException(f"Invalid {label} JSON: {e}") from e
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------
|
|
49
|
+
# Commands
|
|
50
|
+
# ---------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.async_command("create")
|
|
54
|
+
async def room_create_command(
|
|
55
|
+
*,
|
|
56
|
+
project_id: ProjectIdOption = None,
|
|
57
|
+
name: Annotated[str, typer.Option(..., help="Room name")],
|
|
58
|
+
if_not_exists: Annotated[
|
|
59
|
+
bool, typer.Option(help="Do not error if the room already exists")
|
|
60
|
+
] = False,
|
|
61
|
+
metadata: Annotated[
|
|
62
|
+
Optional[str], typer.Option(help="Optional JSON object for room metadata")
|
|
63
|
+
] = None,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Create a room in the project.
|
|
67
|
+
"""
|
|
68
|
+
account_client = await get_client()
|
|
69
|
+
try:
|
|
70
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
71
|
+
|
|
72
|
+
meta_obj = _maybe_parse_json("metadata", metadata)
|
|
73
|
+
|
|
74
|
+
print(f"[bold green]Creating room {name}[/bold green]")
|
|
75
|
+
room = await account_client.create_room(
|
|
76
|
+
project_id=project_id,
|
|
77
|
+
name=name,
|
|
78
|
+
if_not_exists=if_not_exists,
|
|
79
|
+
metadata=meta_obj,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
print(
|
|
83
|
+
json.dumps(
|
|
84
|
+
{"id": room.id, "name": room.name, "metadata": room.metadata}, indent=2
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
except RoomException as ex:
|
|
89
|
+
print(f"[red]{ex}[/red]")
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
finally:
|
|
92
|
+
await account_client.close()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.async_command("delete")
|
|
96
|
+
async def room_delete_command(
|
|
97
|
+
*,
|
|
98
|
+
project_id: ProjectIdOption = None,
|
|
99
|
+
id: Annotated[Optional[str], typer.Option(help="Room ID (preferred)")] = None,
|
|
100
|
+
name: Optional[str] = None,
|
|
101
|
+
):
|
|
102
|
+
"""
|
|
103
|
+
Delete a room by ID (or by name if --name is supplied).
|
|
104
|
+
"""
|
|
105
|
+
account_client = await get_client()
|
|
106
|
+
try:
|
|
107
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
108
|
+
room_name = resolve_room(name) if name else None
|
|
109
|
+
rid = await _resolve_room_id_or_fail(
|
|
110
|
+
account_client, project_id=project_id, room_id=id, room_name=room_name
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
print(f"[bold yellow]Deleting room id={rid}...[/bold yellow]")
|
|
114
|
+
await account_client.delete_room(project_id=project_id, room_id=rid)
|
|
115
|
+
print("[bold cyan]Room deleted.[/bold cyan]")
|
|
116
|
+
except RoomException as ex:
|
|
117
|
+
print(f"[red]{ex}[/red]")
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
finally:
|
|
120
|
+
await account_client.close()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.async_command("update")
|
|
124
|
+
async def room_update_command(
|
|
125
|
+
*,
|
|
126
|
+
project_id: ProjectIdOption = None,
|
|
127
|
+
id: Annotated[Optional[str], typer.Option(help="Room ID (preferred)")] = None,
|
|
128
|
+
name: Optional[str] = None,
|
|
129
|
+
new_name: Annotated[str, typer.Option(..., help="New room name")],
|
|
130
|
+
):
|
|
131
|
+
"""
|
|
132
|
+
Update a room's name (ID is preferred; name will be resolved to ID if needed).
|
|
133
|
+
"""
|
|
134
|
+
account_client = await get_client()
|
|
135
|
+
try:
|
|
136
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
137
|
+
room_name = resolve_room(name) if name else None
|
|
138
|
+
rid = await _resolve_room_id_or_fail(
|
|
139
|
+
account_client, project_id=project_id, room_id=id, room_name=room_name
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
print(
|
|
143
|
+
f"[bold green]Updating room id={rid} -> name='{new_name}'...[/bold green]"
|
|
144
|
+
)
|
|
145
|
+
await account_client.update_room(
|
|
146
|
+
project_id=project_id, room_id=rid, name=new_name
|
|
147
|
+
)
|
|
148
|
+
print("[bold cyan]Room updated.[/bold cyan]")
|
|
149
|
+
except RoomException as ex:
|
|
150
|
+
print(f"[red]{ex}[/red]")
|
|
151
|
+
raise typer.Exit(1)
|
|
152
|
+
finally:
|
|
153
|
+
await account_client.close()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@app.async_command("list")
|
|
157
|
+
async def room_list_command(
|
|
158
|
+
*,
|
|
159
|
+
project_id: ProjectIdOption = None,
|
|
160
|
+
limit: Annotated[
|
|
161
|
+
int, typer.Option(help="Max rooms to return", min=1, max=500)
|
|
162
|
+
] = 50,
|
|
163
|
+
offset: Annotated[int, typer.Option(help="Offset for pagination", min=0)] = 0,
|
|
164
|
+
order_by: Annotated[
|
|
165
|
+
str, typer.Option(help='Order by column (e.g. "room_name", "created_at")')
|
|
166
|
+
] = "room_name",
|
|
167
|
+
):
|
|
168
|
+
"""
|
|
169
|
+
List rooms in the project.
|
|
170
|
+
"""
|
|
171
|
+
account_client = await get_client()
|
|
172
|
+
try:
|
|
173
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
174
|
+
print("[bold green]Fetching rooms...[/bold green]")
|
|
175
|
+
|
|
176
|
+
rooms = await account_client.list_rooms(
|
|
177
|
+
project_id=project_id,
|
|
178
|
+
limit=limit,
|
|
179
|
+
offset=offset,
|
|
180
|
+
order_by=order_by,
|
|
181
|
+
)
|
|
182
|
+
output = [{"id": r.id, "name": r.name, "metadata": r.metadata} for r in rooms]
|
|
183
|
+
print(json.dumps(output, indent=2))
|
|
184
|
+
except RoomException as ex:
|
|
185
|
+
print(f"[red]{ex}[/red]")
|
|
186
|
+
raise typer.Exit(1)
|
|
187
|
+
finally:
|
|
188
|
+
await account_client.close()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@app.async_command("get")
|
|
192
|
+
async def room_get_command(
|
|
193
|
+
*,
|
|
194
|
+
project_id: ProjectIdOption = None,
|
|
195
|
+
name: Optional[str] = None,
|
|
196
|
+
):
|
|
197
|
+
"""
|
|
198
|
+
Get a single room by name (handy for resolving the ID).
|
|
199
|
+
"""
|
|
200
|
+
account_client = await get_client()
|
|
201
|
+
try:
|
|
202
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
203
|
+
room_name = resolve_room(name)
|
|
204
|
+
|
|
205
|
+
print(f"[bold green]Fetching room '{room_name}'...[/bold green]")
|
|
206
|
+
r = await account_client.get_room(project_id=project_id, name=room_name)
|
|
207
|
+
print(
|
|
208
|
+
json.dumps({"id": r.id, "name": r.name, "metadata": r.metadata}, indent=2)
|
|
209
|
+
)
|
|
210
|
+
except RoomException as ex:
|
|
211
|
+
print(f"[red]{ex}[/red]")
|
|
212
|
+
raise typer.Exit(1)
|
|
213
|
+
finally:
|
|
214
|
+
await account_client.close()
|