meshagent-cli 0.0.29__tar.gz → 0.0.31__tar.gz
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.
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/PKG-INFO +6 -5
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/api_keys.py +58 -2
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/auth.py +1 -1
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/call.py +2 -1
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/cli.py +24 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/cli_mcp.py +47 -3
- meshagent_cli-0.0.31/meshagent/cli/projects.py +98 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/services.py +26 -28
- meshagent_cli-0.0.31/meshagent/cli/tty.py +122 -0
- meshagent_cli-0.0.31/meshagent/cli/version.py +1 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/voicebot.py +4 -2
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent_cli.egg-info/PKG-INFO +6 -5
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent_cli.egg-info/SOURCES.txt +1 -0
- meshagent_cli-0.0.31/meshagent_cli.egg-info/requires.txt +8 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/pyproject.toml +5 -4
- meshagent_cli-0.0.29/meshagent/cli/projects.py +0 -36
- meshagent_cli-0.0.29/meshagent/cli/version.py +0 -1
- meshagent_cli-0.0.29/meshagent_cli.egg-info/requires.txt +0 -7
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/README.md +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/__init__.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/agent.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/async_typer.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/auth_async.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/chatbot.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/cli_secrets.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/developer.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/helper.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/messaging.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/participant_token.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/sessions.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/storage.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent/cli/webhook.py +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent_cli.egg-info/dependency_links.txt +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent_cli.egg-info/entry_points.txt +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/meshagent_cli.egg-info/top_level.txt +0 -0
- {meshagent_cli-0.0.29 → meshagent_cli-0.0.31}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.31
|
|
4
4
|
Summary: CLI for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -10,11 +10,12 @@ Requires-Python: >=3.12
|
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
Requires-Dist: typer~=0.15.3
|
|
12
12
|
Requires-Dist: pydantic-yaml~=1.4.0
|
|
13
|
-
Requires-Dist: meshagent-api~=0.0.
|
|
14
|
-
Requires-Dist: meshagent-agents~=0.0.
|
|
15
|
-
Requires-Dist: meshagent-tools~=0.0.
|
|
16
|
-
Requires-Dist: meshagent-mcp~=0.0.
|
|
13
|
+
Requires-Dist: meshagent-api~=0.0.31
|
|
14
|
+
Requires-Dist: meshagent-agents~=0.0.31
|
|
15
|
+
Requires-Dist: meshagent-tools~=0.0.31
|
|
16
|
+
Requires-Dist: meshagent-mcp~=0.0.31
|
|
17
17
|
Requires-Dist: supabase~=2.15.1
|
|
18
|
+
Requires-Dist: fastmcp~=2.8.1
|
|
18
19
|
|
|
19
20
|
### Meshagent CLI
|
|
20
21
|
|
|
@@ -56,16 +56,72 @@ async def show(*, project_id: str = None, api_key_id: str):
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
@app.async_command("activate")
|
|
59
|
-
async def activate(
|
|
59
|
+
async def activate(
|
|
60
|
+
api_key_id: str | None = typer.Argument(None),
|
|
61
|
+
project_id: str = None,
|
|
62
|
+
interactive: bool = typer.Option(
|
|
63
|
+
False,
|
|
64
|
+
"-i",
|
|
65
|
+
"--interactive",
|
|
66
|
+
help="Interactively select or create an api key",
|
|
67
|
+
),
|
|
68
|
+
):
|
|
60
69
|
client = await get_client()
|
|
61
70
|
try:
|
|
62
71
|
project_id = await resolve_project_id(project_id)
|
|
72
|
+
if interactive:
|
|
73
|
+
response = await client.list_project_api_keys(project_id=project_id)
|
|
74
|
+
api_keys = response["keys"]
|
|
75
|
+
|
|
76
|
+
if not api_keys:
|
|
77
|
+
if typer.confirm(
|
|
78
|
+
"There are no API keys. Would you like to create one?",
|
|
79
|
+
default=True,
|
|
80
|
+
):
|
|
81
|
+
name = typer.prompt("API key name")
|
|
82
|
+
created = await client.create_project_api_key(
|
|
83
|
+
project_id=project_id,
|
|
84
|
+
name=name,
|
|
85
|
+
description="",
|
|
86
|
+
)
|
|
87
|
+
api_key_id = created["id"]
|
|
88
|
+
else:
|
|
89
|
+
raise typer.Exit(code=0)
|
|
90
|
+
else:
|
|
91
|
+
for idx, key in enumerate(api_keys, start=1):
|
|
92
|
+
print(f"[{idx}] {key['name']} ({key['id']})")
|
|
93
|
+
new_key_index = len(api_keys) + 1
|
|
94
|
+
print(f"[{new_key_index}] Create a new api key")
|
|
95
|
+
exit_index = new_key_index + 1
|
|
96
|
+
print(f"[{exit_index}] Exit")
|
|
97
|
+
|
|
98
|
+
choice = typer.prompt("Select an api key", type=int)
|
|
99
|
+
if choice == exit_index:
|
|
100
|
+
return
|
|
101
|
+
elif choice == new_key_index:
|
|
102
|
+
name = typer.prompt("API key name")
|
|
103
|
+
created = await client.create_project_api_key(
|
|
104
|
+
project_id=project_id,
|
|
105
|
+
name=name,
|
|
106
|
+
description="",
|
|
107
|
+
)
|
|
108
|
+
api_key_id = created["id"]
|
|
109
|
+
elif 1 <= choice <= len(api_keys):
|
|
110
|
+
api_key_id = api_keys[choice - 1]["id"]
|
|
111
|
+
else:
|
|
112
|
+
print("[red]Invalid selection[/red]")
|
|
113
|
+
raise typer.Exit(code=1)
|
|
114
|
+
|
|
115
|
+
if api_key_id is None and not interactive:
|
|
116
|
+
print("[red]api_key_id required[/red]")
|
|
117
|
+
raise typer.Exit(code=1)
|
|
118
|
+
|
|
63
119
|
response = await client.list_project_api_keys(project_id=project_id)
|
|
64
120
|
api_keys = response["keys"]
|
|
65
121
|
for api_key in api_keys:
|
|
66
122
|
if api_key["id"] == api_key_id:
|
|
67
123
|
await set_active_api_key(project_id=project_id, api_key_id=api_key_id)
|
|
68
|
-
return
|
|
124
|
+
return api_key_id
|
|
69
125
|
|
|
70
126
|
print(f"[red]Invalid api key id or project id: {project_id}[/red]")
|
|
71
127
|
raise typer.Exit(code=1)
|
|
@@ -16,7 +16,7 @@ async def login():
|
|
|
16
16
|
print('You have been logged in, but you haven''t activated a project yet, list your projects with "meshagent project list" and then activate one with "meshagent project activate PROJECT_ID"')
|
|
17
17
|
|
|
18
18
|
@app.async_command("logout")
|
|
19
|
-
async def
|
|
19
|
+
async def logout():
|
|
20
20
|
await auth_async.logout()
|
|
21
21
|
|
|
22
22
|
@app.async_command("whoami")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import typer
|
|
2
|
+
import asyncio
|
|
2
3
|
|
|
3
4
|
from meshagent.cli import auth
|
|
4
5
|
from meshagent.cli import api_keys
|
|
@@ -16,6 +17,7 @@ from meshagent.cli import call
|
|
|
16
17
|
from meshagent.cli import cli_mcp
|
|
17
18
|
from meshagent.cli import chatbot
|
|
18
19
|
from meshagent.cli import voicebot
|
|
20
|
+
from meshagent.cli import tty
|
|
19
21
|
|
|
20
22
|
app = typer.Typer()
|
|
21
23
|
app.add_typer(call.app, name="call")
|
|
@@ -34,6 +36,28 @@ app.add_typer(cli_secrets.app, name="secret")
|
|
|
34
36
|
app.add_typer(cli_mcp.app, name="mcp")
|
|
35
37
|
app.add_typer(chatbot.app, name="chatbot")
|
|
36
38
|
app.add_typer(voicebot.app, name="voicebot")
|
|
39
|
+
app.add_typer(tty.app, name="tty")
|
|
40
|
+
|
|
41
|
+
def _run_async(coro):
|
|
42
|
+
asyncio.run(coro)
|
|
43
|
+
|
|
44
|
+
@app.command("setup")
|
|
45
|
+
def setup_command():
|
|
46
|
+
"""Perform initial login and project/api key activation."""
|
|
47
|
+
|
|
48
|
+
async def runner():
|
|
49
|
+
await auth.login()
|
|
50
|
+
print("Activate a project...")
|
|
51
|
+
project_id = await projects.activate(None, interactive=True)
|
|
52
|
+
if project_id is None:
|
|
53
|
+
print("You have choosen to not activate a project. Exiting.")
|
|
54
|
+
if project_id is not None:
|
|
55
|
+
print("Activate an api-key...")
|
|
56
|
+
api_key_id = await api_keys.activate(None, interactive=True)
|
|
57
|
+
if api_key_id is None:
|
|
58
|
+
print("You have choosen to not activate an api-key. Exiting.")
|
|
59
|
+
|
|
60
|
+
_run_async(runner())
|
|
37
61
|
|
|
38
62
|
if __name__ == "__main__":
|
|
39
63
|
app()
|
|
@@ -22,7 +22,6 @@ from meshagent.api.services import ServiceHost
|
|
|
22
22
|
import os
|
|
23
23
|
|
|
24
24
|
from meshagent.cli.services import _kv_to_dict
|
|
25
|
-
|
|
26
25
|
import shlex
|
|
27
26
|
|
|
28
27
|
app = async_typer.AsyncTyper()
|
|
@@ -107,10 +106,55 @@ async def stdio(*, project_id: str = None, room: Annotated[str, typer.Option()],
|
|
|
107
106
|
finally:
|
|
108
107
|
await account_client.close()
|
|
109
108
|
|
|
109
|
+
@app.async_command("http-proxy")
|
|
110
|
+
async def stdio_host(*, command: Annotated[str, typer.Option()], host: Annotated[Optional[str], typer.Option()] = None, port: Annotated[Optional[int], typer.Option()] = None, path: Annotated[Optional[str], typer.Option()] = None, name: Annotated[Optional[str], typer.Option()] = None, env: Annotated[List[str], typer.Option("--env", "-e", help="KEY=VALUE")] = []):
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
from fastmcp import FastMCP, Client
|
|
113
|
+
from fastmcp.client.transports import StdioTransport
|
|
114
|
+
|
|
115
|
+
parsed_command = shlex.split(command)
|
|
116
|
+
|
|
117
|
+
# Create a client that connects to the original server
|
|
118
|
+
proxy_client = Client(
|
|
119
|
+
transport = StdioTransport(parsed_command[0], parsed_command[1:], _kv_to_dict(env)),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if name == None:
|
|
123
|
+
name = "Stdio-to-Streamable Http Proxy"
|
|
124
|
+
|
|
125
|
+
# Create a proxy server that connects to the client and exposes its capabilities
|
|
126
|
+
proxy = FastMCP.as_proxy(proxy_client, name=name)
|
|
127
|
+
if path == None:
|
|
128
|
+
path = "/mcp"
|
|
129
|
+
|
|
130
|
+
await proxy.run_async(transport='streamable-http', host=host, port=port, path=path)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@app.async_command("sse-proxy")
|
|
134
|
+
async def stdio_host(*, command: Annotated[str, typer.Option()], host: Annotated[Optional[str], typer.Option()] = None, port: Annotated[Optional[int], typer.Option()] = None, path: Annotated[Optional[str], typer.Option()] = None, name: Annotated[Optional[str], typer.Option()] = None, env: Annotated[List[str], typer.Option("--env", "-e", help="KEY=VALUE")] = []):
|
|
135
|
+
|
|
136
|
+
from fastmcp import FastMCP, Client
|
|
137
|
+
from fastmcp.client.transports import StdioTransport
|
|
138
|
+
|
|
139
|
+
parsed_command = shlex.split(command)
|
|
113
140
|
|
|
141
|
+
# Create a client that connects to the original server
|
|
142
|
+
proxy_client = Client(
|
|
143
|
+
transport = StdioTransport(parsed_command[0], parsed_command[1:], _kv_to_dict(env)),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if name == None:
|
|
147
|
+
name = "Stdio-to-SSE Proxy"
|
|
148
|
+
|
|
149
|
+
# Create a proxy server that connects to the client and exposes its capabilities
|
|
150
|
+
proxy = FastMCP.as_proxy(proxy_client, name=name)
|
|
151
|
+
if path == None:
|
|
152
|
+
path = "/sse"
|
|
153
|
+
|
|
154
|
+
await proxy.run_async(transport='sse', host=host, port=port, path=path)
|
|
155
|
+
|
|
156
|
+
@app.async_command("stdio-service")
|
|
157
|
+
async def stdio_host(*, command: Annotated[str, typer.Option()], host: Annotated[Optional[str], typer.Option()] = None, port: Annotated[Optional[int], typer.Option()] = None, webhook_secret: Annotated[Optional[str], typer.Option()] = None, path: Annotated[Optional[str], typer.Option()] = None, toolkit_name: Annotated[Optional[str], typer.Option()] = None, env: Annotated[List[str], typer.Option("--env", "-e", help="KEY=VALUE")] = []):
|
|
114
158
|
try:
|
|
115
159
|
|
|
116
160
|
parsed_command = shlex.split(command)
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
|
|
11
|
+
app = async_typer.AsyncTyper()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.async_command("create")
|
|
15
|
+
async def create(name: str):
|
|
16
|
+
client = await get_client()
|
|
17
|
+
try:
|
|
18
|
+
result = await client.create_project(name)
|
|
19
|
+
print(f"[green]Project created:[/] {result["id"]}")
|
|
20
|
+
finally:
|
|
21
|
+
await client.close()
|
|
22
|
+
|
|
23
|
+
@app.async_command("list")
|
|
24
|
+
async def list():
|
|
25
|
+
client = await get_client()
|
|
26
|
+
projects = await client.list_projects()
|
|
27
|
+
active_project = await get_active_project()
|
|
28
|
+
for project in projects["projects"]:
|
|
29
|
+
if project["id"] == active_project:
|
|
30
|
+
project["name"] = "*" + project["name"]
|
|
31
|
+
|
|
32
|
+
print_json_table(projects["projects"], "id", "name")
|
|
33
|
+
await client.close()
|
|
34
|
+
|
|
35
|
+
@app.async_command("activate")
|
|
36
|
+
async def activate(
|
|
37
|
+
project_id: str | None = typer.Argument(None),
|
|
38
|
+
interactive: bool = typer.Option(
|
|
39
|
+
False,
|
|
40
|
+
"-i",
|
|
41
|
+
"--interactive",
|
|
42
|
+
help="Interactively select or create a project",
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
client = await get_client()
|
|
46
|
+
try:
|
|
47
|
+
if interactive:
|
|
48
|
+
response = await client.list_projects()
|
|
49
|
+
projects = response["projects"]
|
|
50
|
+
|
|
51
|
+
if not projects:
|
|
52
|
+
if typer.confirm(
|
|
53
|
+
"There are no projects. Would you like to create one?",
|
|
54
|
+
default=True,
|
|
55
|
+
):
|
|
56
|
+
name = typer.prompt("Project name")
|
|
57
|
+
created = await client.create_project(name)
|
|
58
|
+
project_id = created["id"]
|
|
59
|
+
else:
|
|
60
|
+
raise typer.Exit(code=0)
|
|
61
|
+
else:
|
|
62
|
+
for idx, proj in enumerate(projects, start=1):
|
|
63
|
+
print(f"[{idx}] {proj['name']} ({proj['id']})")
|
|
64
|
+
new_project_index = len(projects) + 1
|
|
65
|
+
print(f"[{new_project_index}] Create a new project")
|
|
66
|
+
exit_index = new_project_index + 1
|
|
67
|
+
print(f"[{exit_index}] Exit")
|
|
68
|
+
|
|
69
|
+
choice = typer.prompt("Select a project", type=int)
|
|
70
|
+
if choice == exit_index:
|
|
71
|
+
return
|
|
72
|
+
elif choice == new_project_index:
|
|
73
|
+
name = typer.prompt("Project name")
|
|
74
|
+
# TODO: validate name
|
|
75
|
+
created = await client.create_project(name)
|
|
76
|
+
project_id = created["id"]
|
|
77
|
+
elif 1 <= choice <= len(projects):
|
|
78
|
+
project_id = projects[choice - 1]["id"]
|
|
79
|
+
else:
|
|
80
|
+
print("[red]Invalid selection[/red]")
|
|
81
|
+
raise typer.Exit(code=1)
|
|
82
|
+
|
|
83
|
+
if project_id is None and not interactive:
|
|
84
|
+
print("[red]project_id required[/red]")
|
|
85
|
+
raise typer.Exit(code=1)
|
|
86
|
+
|
|
87
|
+
if project_id is not None:
|
|
88
|
+
projects = (await client.list_projects())["projects"]
|
|
89
|
+
for project in projects:
|
|
90
|
+
if project["id"] == project_id:
|
|
91
|
+
await set_active_project(project_id=project_id)
|
|
92
|
+
return project_id
|
|
93
|
+
|
|
94
|
+
print(f"[red]Invalid project id: {project_id}[/red]")
|
|
95
|
+
raise typer.Exit(code=1)
|
|
96
|
+
finally:
|
|
97
|
+
await client.close()
|
|
98
|
+
|
|
@@ -15,6 +15,7 @@ from meshagent.cli.helper import get_client, print_json_table, resolve_project_i
|
|
|
15
15
|
from meshagent.api import ParticipantToken, RoomClient, WebSocketClientProtocol, websocket_protocol, websocket_room_url, meshagent_base_url
|
|
16
16
|
# Pydantic basemodels
|
|
17
17
|
from meshagent.api.accounts_client import Service, Port
|
|
18
|
+
from meshagent.cloud.accounts import Services
|
|
18
19
|
|
|
19
20
|
app = async_typer.AsyncTyper()
|
|
20
21
|
|
|
@@ -38,13 +39,14 @@ class PortSpec(pydantic.BaseModel):
|
|
|
38
39
|
"""
|
|
39
40
|
CLI schema for --port.
|
|
40
41
|
Example:
|
|
41
|
-
--port num=8080 type=webserver liveness=/health path=
|
|
42
|
+
--port num=8080 type=webserver liveness=/health path=/agent participant_name=myname
|
|
42
43
|
"""
|
|
43
44
|
|
|
44
45
|
num: PositiveInt
|
|
45
46
|
type: Literal["mcp.sse", "meshagent.callable", "http", "tcp"]
|
|
46
47
|
liveness: str | None = None
|
|
47
48
|
participant_name: str | None = None
|
|
49
|
+
path: str | None = None
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def _parse_port_spec(spec: str) -> PortSpec:
|
|
@@ -76,7 +78,7 @@ async def service_create(
|
|
|
76
78
|
project_id: str = None,
|
|
77
79
|
name: Annotated[str, typer.Option(help="Friendly service name")],
|
|
78
80
|
image: Annotated[str, typer.Option(help="Container image reference")],
|
|
79
|
-
role: Annotated[str, typer.Option(help="Service role (agent|tool)")],
|
|
81
|
+
role: Annotated[str, typer.Option(help="Service role (agent|tool)")] = None,
|
|
80
82
|
pull_secret: Annotated[
|
|
81
83
|
Optional[str],
|
|
82
84
|
typer.Option("--pull-secret", help="Secret ID for registry"),
|
|
@@ -99,10 +101,10 @@ async def service_create(
|
|
|
99
101
|
"-p",
|
|
100
102
|
help=(
|
|
101
103
|
"Repeatable. Example:\n"
|
|
102
|
-
' -p "num=8080 type=[mcp.sse | meshagent.callable | http | tcp] liveness=/health"'
|
|
104
|
+
' -p "num=8080 type=[mcp.sse | meshagent.callable | http | tcp] liveness=/health path=/agent participant_name=myname"'
|
|
103
105
|
),
|
|
104
106
|
),
|
|
105
|
-
] =
|
|
107
|
+
] = ...,
|
|
106
108
|
):
|
|
107
109
|
"""Create a service attached to the project."""
|
|
108
110
|
client = await get_client()
|
|
@@ -117,6 +119,7 @@ async def service_create(
|
|
|
117
119
|
type=ps.type,
|
|
118
120
|
liveness_path=ps.liveness,
|
|
119
121
|
participant_name=ps.participant_name,
|
|
122
|
+
path=ps.path,
|
|
120
123
|
)
|
|
121
124
|
for ps in port_specs
|
|
122
125
|
} or None
|
|
@@ -159,7 +162,7 @@ async def service_test(
|
|
|
159
162
|
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
160
163
|
room: Annotated[str, typer.Option(help="A room name to test the service in (must not be currently running)")],
|
|
161
164
|
name: Annotated[str, typer.Option(help="Friendly service name")],
|
|
162
|
-
role: Annotated[str, typer.Option(help="Service role (agent|tool)")],
|
|
165
|
+
role: Annotated[str, typer.Option(help="Service role (agent|tool)")] = None,
|
|
163
166
|
image: Annotated[str, typer.Option(help="Container image reference")],
|
|
164
167
|
pull_secret: Annotated[
|
|
165
168
|
Optional[str],
|
|
@@ -183,10 +186,10 @@ async def service_test(
|
|
|
183
186
|
"-p",
|
|
184
187
|
help=(
|
|
185
188
|
"Repeatable. Example:\n"
|
|
186
|
-
' -p "num=8080 type=[mcp.sse | meshagent.callable | http | tcp] liveness=/health"'
|
|
189
|
+
' -p "num=8080 type=[mcp.sse | meshagent.callable | http | tcp] liveness=/health path=/agent participant_name=myname"'
|
|
187
190
|
),
|
|
188
191
|
),
|
|
189
|
-
] =
|
|
192
|
+
] = ...,
|
|
190
193
|
timeout: Annotated[
|
|
191
194
|
Optional[int],
|
|
192
195
|
typer.Option("--timeout", help="The maximum time that this room should run (default 1hr)"),
|
|
@@ -195,7 +198,7 @@ async def service_test(
|
|
|
195
198
|
|
|
196
199
|
|
|
197
200
|
"""Create a service attached to the project."""
|
|
198
|
-
|
|
201
|
+
my_client = await get_client()
|
|
199
202
|
try:
|
|
200
203
|
project_id = await resolve_project_id(project_id)
|
|
201
204
|
|
|
@@ -209,6 +212,7 @@ async def service_test(
|
|
|
209
212
|
type=ps.type,
|
|
210
213
|
liveness_path=ps.liveness,
|
|
211
214
|
participant_name=ps.participant_name,
|
|
215
|
+
path=ps.path,
|
|
212
216
|
)
|
|
213
217
|
for ps in port_specs
|
|
214
218
|
} or None
|
|
@@ -245,7 +249,7 @@ async def service_test(
|
|
|
245
249
|
|
|
246
250
|
print("[bold green]Connecting to room...[/bold green]")
|
|
247
251
|
|
|
248
|
-
key = (await
|
|
252
|
+
key = (await my_client.decrypt_project_api_key(project_id=project_id, id=api_key_id))["token"]
|
|
249
253
|
|
|
250
254
|
async with RoomClient(
|
|
251
255
|
protocol=WebSocketClientProtocol(
|
|
@@ -254,11 +258,7 @@ async def service_test(
|
|
|
254
258
|
)
|
|
255
259
|
) as client:
|
|
256
260
|
|
|
257
|
-
print(f"[green]
|
|
258
|
-
try:
|
|
259
|
-
await client.protocol.wait_for_close()
|
|
260
|
-
except KeyboardInterrupt:
|
|
261
|
-
pass
|
|
261
|
+
print(f"[green]Your test room '{room}' has been started. It will time out after a few minutes if you do not join it.[/green]")
|
|
262
262
|
|
|
263
263
|
|
|
264
264
|
except ClientResponseError as exc:
|
|
@@ -269,7 +269,7 @@ async def service_test(
|
|
|
269
269
|
|
|
270
270
|
|
|
271
271
|
finally:
|
|
272
|
-
await
|
|
272
|
+
await my_client.close()
|
|
273
273
|
|
|
274
274
|
|
|
275
275
|
|
|
@@ -292,7 +292,7 @@ async def service_show(
|
|
|
292
292
|
|
|
293
293
|
|
|
294
294
|
@app.async_command("list")
|
|
295
|
-
async def service_list(*, project_id: str = None):
|
|
295
|
+
async def service_list(*, project_id: str = None, o: Annotated[str, typer.Option(help="output format [json|table]")]):
|
|
296
296
|
"""List all services for the project."""
|
|
297
297
|
client = await get_client()
|
|
298
298
|
try:
|
|
@@ -300,18 +300,16 @@ async def service_list(*, project_id: str = None):
|
|
|
300
300
|
services: list[Service] = await client.list_services(
|
|
301
301
|
project_id=project_id
|
|
302
302
|
) # → List[Service]
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
"ports",
|
|
314
|
-
)
|
|
303
|
+
|
|
304
|
+
if o == "json":
|
|
305
|
+
print(Services(services=services).model_dump_json(indent=2))
|
|
306
|
+
else:
|
|
307
|
+
print_json_table(
|
|
308
|
+
[svc.model_dump(mode="json") for svc in services],
|
|
309
|
+
"id",
|
|
310
|
+
"name",
|
|
311
|
+
"image"
|
|
312
|
+
)
|
|
315
313
|
finally:
|
|
316
314
|
await client.close()
|
|
317
315
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import sys
|
|
3
|
+
import tty
|
|
4
|
+
import termios
|
|
5
|
+
from typing import Annotated, Optional
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import typer
|
|
9
|
+
from rich import print
|
|
10
|
+
import aiohttp
|
|
11
|
+
|
|
12
|
+
from meshagent.api import RoomClient, ParticipantToken
|
|
13
|
+
from meshagent.cli import async_typer
|
|
14
|
+
from meshagent.cli.helper import (
|
|
15
|
+
get_client,
|
|
16
|
+
print_json_table,
|
|
17
|
+
set_active_project,
|
|
18
|
+
resolve_project_id,
|
|
19
|
+
resolve_api_key,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
app = async_typer.AsyncTyper()
|
|
25
|
+
|
|
26
|
+
@app.async_command("connect")
|
|
27
|
+
async def tty_command(
|
|
28
|
+
*,
|
|
29
|
+
project_id: str = None,
|
|
30
|
+
room: Annotated[str, typer.Option()],
|
|
31
|
+
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
32
|
+
|
|
33
|
+
):
|
|
34
|
+
"""Open an interactive websocket‑based TTY."""
|
|
35
|
+
client = await get_client()
|
|
36
|
+
try:
|
|
37
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
38
|
+
api_key_id = await resolve_api_key(project_id=project_id, api_key_id=api_key_id)
|
|
39
|
+
|
|
40
|
+
key = (await client.decrypt_project_api_key(project_id=project_id, id=api_key_id))["token"]
|
|
41
|
+
|
|
42
|
+
token = ParticipantToken(
|
|
43
|
+
name="tty",
|
|
44
|
+
project_id=project_id,
|
|
45
|
+
api_key_id=api_key_id
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
token.add_role_grant(role="user")
|
|
49
|
+
|
|
50
|
+
token.add_room_grant(room)
|
|
51
|
+
|
|
52
|
+
ws_url = f"{os.getenv("MESHAGENT_API_URL").replace("http","ws",1)}/rooms/{room}/tty?token={token.to_jwt(token=key)}"
|
|
53
|
+
|
|
54
|
+
print(f"[bold green]Connecting to[/bold green] {room}")
|
|
55
|
+
|
|
56
|
+
# Save current terminal settings so we can restore them later.
|
|
57
|
+
old_tty_settings = termios.tcgetattr(sys.stdin)
|
|
58
|
+
try:
|
|
59
|
+
async with aiohttp.ClientSession() as session:
|
|
60
|
+
|
|
61
|
+
async with session.ws_connect(ws_url) as websocket:
|
|
62
|
+
|
|
63
|
+
print(f"[bold green]Connected to[/bold green] {room}")
|
|
64
|
+
|
|
65
|
+
tty.setraw(sys.stdin)
|
|
66
|
+
|
|
67
|
+
async def recv_from_websocket():
|
|
68
|
+
async for message in websocket:
|
|
69
|
+
|
|
70
|
+
if message.type == aiohttp.WSMsgType.CLOSE:
|
|
71
|
+
await websocket.close()
|
|
72
|
+
|
|
73
|
+
elif message.type == aiohttp.WSMsgType.ERROR:
|
|
74
|
+
await websocket.close()
|
|
75
|
+
|
|
76
|
+
data : bytes = message.data
|
|
77
|
+
sys.stdout.write(data.decode("utf-8"))
|
|
78
|
+
sys.stdout.flush()
|
|
79
|
+
|
|
80
|
+
async def send_to_websocket():
|
|
81
|
+
loop = asyncio.get_running_loop()
|
|
82
|
+
|
|
83
|
+
reader = asyncio.StreamReader()
|
|
84
|
+
protocol = asyncio.StreamReaderProtocol(reader)
|
|
85
|
+
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
|
|
86
|
+
|
|
87
|
+
while True:
|
|
88
|
+
# Read one character at a time from stdin without blocking the event loop.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
data = await reader.read(1)
|
|
92
|
+
if not data:
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
if websocket.closed:
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
if data == b'\x03':
|
|
99
|
+
print("<CTRL-C>\n")
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
if data:
|
|
103
|
+
await websocket.send_bytes(data)
|
|
104
|
+
else:
|
|
105
|
+
await websocket.close(code=1000)
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
done, pending = await asyncio.wait([
|
|
109
|
+
asyncio.create_task(recv_from_websocket()),
|
|
110
|
+
asyncio.create_task(send_to_websocket())
|
|
111
|
+
], return_when=asyncio.FIRST_COMPLETED)
|
|
112
|
+
|
|
113
|
+
for task in pending:
|
|
114
|
+
task.cancel()
|
|
115
|
+
|
|
116
|
+
finally:
|
|
117
|
+
# Restore original terminal settings even if the coroutine is cancelled.
|
|
118
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty_settings)
|
|
119
|
+
|
|
120
|
+
finally:
|
|
121
|
+
|
|
122
|
+
await client.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.31"
|
|
@@ -42,7 +42,8 @@ async def make_call(
|
|
|
42
42
|
rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
|
|
43
43
|
toolkit: Annotated[List[str], typer.Option("--toolkit", "-t", help="the name or url of a required toolkit")] = [],
|
|
44
44
|
schema: Annotated[List[str], typer.Option("--schema", "-s", help="the name or url of a required schema")] = [],
|
|
45
|
-
|
|
45
|
+
auto_greet_message: Annotated[Optional[str], typer.Option()] = None,
|
|
46
|
+
auto_greet_prompt: Annotated[Optional[str], typer.Option()] = None,
|
|
46
47
|
):
|
|
47
48
|
account_client = await get_client()
|
|
48
49
|
try:
|
|
@@ -75,7 +76,8 @@ async def make_call(
|
|
|
75
76
|
requirements.append(RequiredSchema(name=t))
|
|
76
77
|
|
|
77
78
|
bot = VoiceBot(
|
|
78
|
-
|
|
79
|
+
auto_greet_message=auto_greet_message,
|
|
80
|
+
auto_greet_prompt=auto_greet_prompt,
|
|
79
81
|
name=agent_name,
|
|
80
82
|
requires=requirements,
|
|
81
83
|
rules=rule if len(rule) > 0 else None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.31
|
|
4
4
|
Summary: CLI for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -10,11 +10,12 @@ Requires-Python: >=3.12
|
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
Requires-Dist: typer~=0.15.3
|
|
12
12
|
Requires-Dist: pydantic-yaml~=1.4.0
|
|
13
|
-
Requires-Dist: meshagent-api~=0.0.
|
|
14
|
-
Requires-Dist: meshagent-agents~=0.0.
|
|
15
|
-
Requires-Dist: meshagent-tools~=0.0.
|
|
16
|
-
Requires-Dist: meshagent-mcp~=0.0.
|
|
13
|
+
Requires-Dist: meshagent-api~=0.0.31
|
|
14
|
+
Requires-Dist: meshagent-agents~=0.0.31
|
|
15
|
+
Requires-Dist: meshagent-tools~=0.0.31
|
|
16
|
+
Requires-Dist: meshagent-mcp~=0.0.31
|
|
17
17
|
Requires-Dist: supabase~=2.15.1
|
|
18
|
+
Requires-Dist: fastmcp~=2.8.1
|
|
18
19
|
|
|
19
20
|
### Meshagent CLI
|
|
20
21
|
|
|
@@ -11,11 +11,12 @@ keywords = []
|
|
|
11
11
|
dependencies = [
|
|
12
12
|
"typer~=0.15.3",
|
|
13
13
|
"pydantic-yaml~=1.4.0",
|
|
14
|
-
"meshagent-api~=0.0.
|
|
15
|
-
"meshagent-agents~=0.0.
|
|
16
|
-
"meshagent-tools~=0.0.
|
|
17
|
-
"meshagent-mcp~=0.0.
|
|
14
|
+
"meshagent-api~=0.0.31",
|
|
15
|
+
"meshagent-agents~=0.0.31",
|
|
16
|
+
"meshagent-tools~=0.0.31",
|
|
17
|
+
"meshagent-mcp~=0.0.31",
|
|
18
18
|
"supabase~=2.15.1",
|
|
19
|
+
"fastmcp~=2.8.1"
|
|
19
20
|
]
|
|
20
21
|
dynamic = ["version", "readme"]
|
|
21
22
|
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
from rich import print
|
|
3
|
-
from meshagent.cli import async_typer
|
|
4
|
-
from meshagent.cli.helper import get_client, print_json_table, set_active_project, get_active_project
|
|
5
|
-
|
|
6
|
-
app = async_typer.AsyncTyper()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@app.async_command("list")
|
|
10
|
-
async def list():
|
|
11
|
-
client = await get_client()
|
|
12
|
-
projects = await client.list_projects()
|
|
13
|
-
active_project = await get_active_project()
|
|
14
|
-
for project in projects["projects"]:
|
|
15
|
-
if project["id"] == active_project:
|
|
16
|
-
project["name"] = "*" + project["name"]
|
|
17
|
-
|
|
18
|
-
print_json_table(projects["projects"], "id", "name")
|
|
19
|
-
await client.close()
|
|
20
|
-
|
|
21
|
-
@app.async_command("activate")
|
|
22
|
-
async def activate(project_id: str):
|
|
23
|
-
client = await get_client()
|
|
24
|
-
try:
|
|
25
|
-
projects = await client.list_projects()
|
|
26
|
-
projects = projects["projects"]
|
|
27
|
-
for project in projects:
|
|
28
|
-
if project["id"] == project_id:
|
|
29
|
-
await set_active_project(project_id=project_id)
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
print(f"[red]Invalid project id: {project_id}[/red]")
|
|
33
|
-
raise typer.Exit(code=1)
|
|
34
|
-
finally:
|
|
35
|
-
await client.close()
|
|
36
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.29"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|