meshagent-cli 0.1.0__tar.gz → 0.2.0__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.1.0 → meshagent_cli-0.2.0}/PKG-INFO +7 -7
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/call.py +1 -1
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/cli.py +6 -3
- meshagent_cli-0.2.0/meshagent/cli/exec.py +286 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/storage.py +1 -1
- meshagent_cli-0.2.0/meshagent/cli/version.py +1 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent_cli.egg-info/PKG-INFO +7 -7
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent_cli.egg-info/SOURCES.txt +1 -1
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent_cli.egg-info/requires.txt +6 -6
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/pyproject.toml +6 -6
- meshagent_cli-0.1.0/meshagent/cli/tty.py +0 -124
- meshagent_cli-0.1.0/meshagent/cli/version.py +0 -1
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/README.md +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/__init__.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/agent.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/api_keys.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/async_typer.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/auth.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/auth_async.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/chatbot.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/cli_mcp.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/cli_secrets.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/developer.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/helper.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/messaging.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/otel.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/participant_token.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/projects.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/services.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/sessions.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/voicebot.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent/cli/webhook.py +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent_cli.egg-info/dependency_links.txt +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent_cli.egg-info/entry_points.txt +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/meshagent_cli.egg-info/top_level.txt +0 -0
- {meshagent_cli-0.1.0 → meshagent_cli-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: CLI for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -10,12 +10,12 @@ Requires-Python: >=3.12
|
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
Requires-Dist: typer~=0.15
|
|
12
12
|
Requires-Dist: pydantic-yaml~=1.4
|
|
13
|
-
Requires-Dist: meshagent-api~=0.
|
|
14
|
-
Requires-Dist: meshagent-agents~=0.
|
|
15
|
-
Requires-Dist: meshagent-computers~=0.
|
|
16
|
-
Requires-Dist: meshagent-openai~=0.
|
|
17
|
-
Requires-Dist: meshagent-tools~=0.
|
|
18
|
-
Requires-Dist: meshagent-mcp~=0.
|
|
13
|
+
Requires-Dist: meshagent-api~=0.2.0
|
|
14
|
+
Requires-Dist: meshagent-agents~=0.2.0
|
|
15
|
+
Requires-Dist: meshagent-computers~=0.2.0
|
|
16
|
+
Requires-Dist: meshagent-openai~=0.2.0
|
|
17
|
+
Requires-Dist: meshagent-tools~=0.2.0
|
|
18
|
+
Requires-Dist: meshagent-mcp~=0.2.0
|
|
19
19
|
Requires-Dist: supabase~=2.15
|
|
20
20
|
Requires-Dist: fastmcp~=2.8
|
|
21
21
|
Requires-Dist: opentelemetry-distro~=0.54b1
|
|
@@ -117,7 +117,7 @@ async def make_call(
|
|
|
117
117
|
)["token"]
|
|
118
118
|
|
|
119
119
|
token = ParticipantToken(
|
|
120
|
-
name=
|
|
120
|
+
name=participant_name, project_id=project_id, api_key_id=api_key_id
|
|
121
121
|
)
|
|
122
122
|
token.add_role_grant(role=role)
|
|
123
123
|
token.add_room_grant(room)
|
|
@@ -2,6 +2,8 @@ import typer
|
|
|
2
2
|
import asyncio
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
+
from meshagent.cli import async_typer
|
|
6
|
+
|
|
5
7
|
from meshagent.cli import auth
|
|
6
8
|
from meshagent.cli import api_keys
|
|
7
9
|
from meshagent.cli import projects
|
|
@@ -18,7 +20,7 @@ from meshagent.cli import call
|
|
|
18
20
|
from meshagent.cli import cli_mcp
|
|
19
21
|
from meshagent.cli import chatbot
|
|
20
22
|
from meshagent.cli import voicebot
|
|
21
|
-
from meshagent.cli import
|
|
23
|
+
from meshagent.cli.exec import register as register_exec
|
|
22
24
|
|
|
23
25
|
from meshagent.cli import otel
|
|
24
26
|
|
|
@@ -38,7 +40,7 @@ otel.init(level=logging.INFO)
|
|
|
38
40
|
logging.getLogger("openai").setLevel(logging.ERROR)
|
|
39
41
|
logging.getLogger("httpx").setLevel(logging.ERROR)
|
|
40
42
|
|
|
41
|
-
app =
|
|
43
|
+
app = async_typer.AsyncTyper()
|
|
42
44
|
app.add_typer(call.app, name="call")
|
|
43
45
|
app.add_typer(auth.app, name="auth")
|
|
44
46
|
app.add_typer(projects.app, name="project")
|
|
@@ -55,7 +57,8 @@ app.add_typer(cli_secrets.app, name="secret")
|
|
|
55
57
|
app.add_typer(cli_mcp.app, name="mcp")
|
|
56
58
|
app.add_typer(chatbot.app, name="chatbot")
|
|
57
59
|
app.add_typer(voicebot.app, name="voicebot")
|
|
58
|
-
|
|
60
|
+
|
|
61
|
+
register_exec(app)
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
def _run_async(coro):
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import tty as _tty
|
|
3
|
+
import termios
|
|
4
|
+
from meshagent.api.websocket_protocol import WebSocketClientProtocol
|
|
5
|
+
from meshagent.api import RoomClient
|
|
6
|
+
from meshagent.api.helpers import websocket_room_url
|
|
7
|
+
from typing import Annotated, Optional
|
|
8
|
+
import asyncio
|
|
9
|
+
import typer
|
|
10
|
+
from rich import print
|
|
11
|
+
import aiohttp
|
|
12
|
+
import struct
|
|
13
|
+
import signal
|
|
14
|
+
import shutil
|
|
15
|
+
import json
|
|
16
|
+
from urllib.parse import quote
|
|
17
|
+
|
|
18
|
+
from meshagent.api import ParticipantToken
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
from meshagent.cli.helper import (
|
|
23
|
+
get_client,
|
|
24
|
+
resolve_project_id,
|
|
25
|
+
resolve_api_key,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register(app: typer.Typer):
|
|
30
|
+
@app.async_command("exec")
|
|
31
|
+
async def exec_command(
|
|
32
|
+
*,
|
|
33
|
+
project_id: str = None,
|
|
34
|
+
room: Annotated[str, typer.Option()],
|
|
35
|
+
name: Annotated[Optional[str], typer.Option()] = None,
|
|
36
|
+
image: Annotated[Optional[str], typer.Option()] = None,
|
|
37
|
+
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
38
|
+
command: Annotated[list[str], typer.Argument(...)] = None,
|
|
39
|
+
tty: bool = False,
|
|
40
|
+
):
|
|
41
|
+
"""Open an interactive websocket‑based TTY."""
|
|
42
|
+
client = await get_client()
|
|
43
|
+
try:
|
|
44
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
45
|
+
api_key_id = await resolve_api_key(
|
|
46
|
+
project_id=project_id, api_key_id=api_key_id
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
token = ParticipantToken(
|
|
50
|
+
name="tty", project_id=project_id, api_key_id=api_key_id
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
key = (
|
|
54
|
+
await client.decrypt_project_api_key(
|
|
55
|
+
project_id=project_id, id=api_key_id
|
|
56
|
+
)
|
|
57
|
+
)["token"]
|
|
58
|
+
|
|
59
|
+
token.add_role_grant(role="user")
|
|
60
|
+
token.add_room_grant(room)
|
|
61
|
+
|
|
62
|
+
ws_url = (
|
|
63
|
+
websocket_room_url(room_name=room)
|
|
64
|
+
+ f"/exec?token={token.to_jwt(token=key)}"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if image:
|
|
68
|
+
ws_url += f"&image={quote(' '.join(image))}"
|
|
69
|
+
|
|
70
|
+
if name:
|
|
71
|
+
ws_url += f"&name={quote(' '.join(name))}"
|
|
72
|
+
|
|
73
|
+
if command and len(command) != 0:
|
|
74
|
+
ws_url += f"&command={quote(' '.join(command))}"
|
|
75
|
+
|
|
76
|
+
if tty:
|
|
77
|
+
if not sys.stdin.isatty():
|
|
78
|
+
print("[red]TTY requested but process is not a TTY[/red]")
|
|
79
|
+
raise typer.Exit(1)
|
|
80
|
+
|
|
81
|
+
ws_url += "&tty=true"
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
if command is None:
|
|
85
|
+
print("[red]TTY required when not executing a command[/red]")
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
|
|
88
|
+
ws_url += "&tty=false"
|
|
89
|
+
|
|
90
|
+
if tty:
|
|
91
|
+
# Save current terminal settings so we can restore them later.
|
|
92
|
+
old_tty_settings = termios.tcgetattr(sys.stdin)
|
|
93
|
+
_tty.setraw(sys.stdin)
|
|
94
|
+
|
|
95
|
+
async with RoomClient(
|
|
96
|
+
protocol=WebSocketClientProtocol(
|
|
97
|
+
url=websocket_room_url(room_name=room),
|
|
98
|
+
token=token.to_jwt(token=key),
|
|
99
|
+
)
|
|
100
|
+
):
|
|
101
|
+
try:
|
|
102
|
+
async with aiohttp.ClientSession() as session:
|
|
103
|
+
async with session.ws_connect(ws_url) as websocket:
|
|
104
|
+
send_queue = asyncio.Queue[bytes]()
|
|
105
|
+
|
|
106
|
+
loop = asyncio.get_running_loop()
|
|
107
|
+
(
|
|
108
|
+
stdout_transport,
|
|
109
|
+
stdout_protocol,
|
|
110
|
+
) = await loop.connect_write_pipe(
|
|
111
|
+
asyncio.streams.FlowControlMixin, sys.stdout
|
|
112
|
+
)
|
|
113
|
+
stdout_writer = asyncio.StreamWriter(
|
|
114
|
+
stdout_transport, stdout_protocol, None, loop
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
(
|
|
118
|
+
stderr_transport,
|
|
119
|
+
stderr_protocol,
|
|
120
|
+
) = await loop.connect_write_pipe(
|
|
121
|
+
asyncio.streams.FlowControlMixin, sys.stderr
|
|
122
|
+
)
|
|
123
|
+
stderr_writer = asyncio.StreamWriter(
|
|
124
|
+
stderr_transport, stderr_protocol, None, loop
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
async def recv_from_websocket():
|
|
128
|
+
while True:
|
|
129
|
+
done, pending = await asyncio.wait(
|
|
130
|
+
[asyncio.create_task(websocket.receive())],
|
|
131
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
first = done.pop()
|
|
135
|
+
|
|
136
|
+
if first == read_stdin_task:
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
message = first.result()
|
|
140
|
+
|
|
141
|
+
if websocket.closed:
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
if message.type == aiohttp.WSMsgType.CLOSE:
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
elif message.type == aiohttp.WSMsgType.CLOSING:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
elif message.type == aiohttp.WSMsgType.ERROR:
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
if not message.data:
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
data: bytes = message.data
|
|
157
|
+
if len(data) > 0:
|
|
158
|
+
if data[0] == 1:
|
|
159
|
+
stderr_writer.write(data)
|
|
160
|
+
await stderr_writer.drain()
|
|
161
|
+
elif data[0] == 0:
|
|
162
|
+
stdout_writer.write(data)
|
|
163
|
+
await stdout_writer.drain()
|
|
164
|
+
else:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"Invalid channel received {data[0]}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
last_size = None
|
|
170
|
+
|
|
171
|
+
async def send_resize(rows, cols):
|
|
172
|
+
nonlocal last_size
|
|
173
|
+
|
|
174
|
+
size = (cols, rows)
|
|
175
|
+
if size == last_size:
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
last_size = size
|
|
179
|
+
|
|
180
|
+
resize_json = json.dumps(
|
|
181
|
+
{"Width": cols, "Height": rows}
|
|
182
|
+
).encode("utf-8")
|
|
183
|
+
payload = struct.pack("B", 4) + resize_json
|
|
184
|
+
send_queue.put_nowait(payload)
|
|
185
|
+
await asyncio.sleep(5)
|
|
186
|
+
|
|
187
|
+
cols, rows = shutil.get_terminal_size(fallback=(24, 80))
|
|
188
|
+
if tty:
|
|
189
|
+
await send_resize(rows, cols)
|
|
190
|
+
|
|
191
|
+
def on_sigwinch():
|
|
192
|
+
cols, rows = shutil.get_terminal_size(fallback=(24, 80))
|
|
193
|
+
task = asyncio.create_task(send_resize(rows, cols))
|
|
194
|
+
|
|
195
|
+
def on_done(t: asyncio.Task):
|
|
196
|
+
t.result()
|
|
197
|
+
|
|
198
|
+
task.add_done_callback(on_done)
|
|
199
|
+
|
|
200
|
+
loop.add_signal_handler(signal.SIGWINCH, on_sigwinch)
|
|
201
|
+
|
|
202
|
+
async def read_stdin():
|
|
203
|
+
loop = asyncio.get_running_loop()
|
|
204
|
+
|
|
205
|
+
reader = asyncio.StreamReader()
|
|
206
|
+
protocol = asyncio.StreamReaderProtocol(reader)
|
|
207
|
+
await loop.connect_read_pipe(
|
|
208
|
+
lambda: protocol, sys.stdin
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
while True:
|
|
212
|
+
# Read one character at a time from stdin without blocking the event loop.
|
|
213
|
+
done, pending = await asyncio.wait(
|
|
214
|
+
[
|
|
215
|
+
asyncio.create_task(reader.read(1)),
|
|
216
|
+
websocket_recv_task,
|
|
217
|
+
],
|
|
218
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
first = done.pop()
|
|
222
|
+
if first == websocket_recv_task:
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
data = first.result()
|
|
226
|
+
if not data:
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
if websocket.closed:
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
if tty:
|
|
233
|
+
if data == b"\x04":
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
if data:
|
|
237
|
+
send_queue.put_nowait(b"\0" + data)
|
|
238
|
+
else:
|
|
239
|
+
break
|
|
240
|
+
|
|
241
|
+
send_queue.put_nowait(b"\0")
|
|
242
|
+
|
|
243
|
+
websocket_recv_task = asyncio.create_task(
|
|
244
|
+
recv_from_websocket()
|
|
245
|
+
)
|
|
246
|
+
read_stdin_task = asyncio.create_task(read_stdin())
|
|
247
|
+
|
|
248
|
+
async def send_to_websocket():
|
|
249
|
+
while True:
|
|
250
|
+
try:
|
|
251
|
+
data = await send_queue.get()
|
|
252
|
+
if websocket.closed:
|
|
253
|
+
break
|
|
254
|
+
|
|
255
|
+
if data is not None:
|
|
256
|
+
await websocket.send_bytes(data)
|
|
257
|
+
|
|
258
|
+
else:
|
|
259
|
+
break
|
|
260
|
+
except asyncio.QueueShutDown:
|
|
261
|
+
break
|
|
262
|
+
|
|
263
|
+
send_to_websocket_task = asyncio.create_task(
|
|
264
|
+
send_to_websocket()
|
|
265
|
+
)
|
|
266
|
+
await asyncio.gather(
|
|
267
|
+
websocket_recv_task,
|
|
268
|
+
read_stdin_task,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
send_queue.shutdown()
|
|
272
|
+
await send_to_websocket_task
|
|
273
|
+
|
|
274
|
+
finally:
|
|
275
|
+
if not sys.stdin.closed and tty:
|
|
276
|
+
# Restore original terminal settings even if the coroutine is cancelled.
|
|
277
|
+
termios.tcsetattr(
|
|
278
|
+
sys.stdin, termios.TCSADRAIN, old_tty_settings
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
print(f"[red]{e}[/red]")
|
|
283
|
+
logging.error("failed", exc_info=e)
|
|
284
|
+
raise typer.Exit(1)
|
|
285
|
+
finally:
|
|
286
|
+
await client.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: CLI for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -10,12 +10,12 @@ Requires-Python: >=3.12
|
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
Requires-Dist: typer~=0.15
|
|
12
12
|
Requires-Dist: pydantic-yaml~=1.4
|
|
13
|
-
Requires-Dist: meshagent-api~=0.
|
|
14
|
-
Requires-Dist: meshagent-agents~=0.
|
|
15
|
-
Requires-Dist: meshagent-computers~=0.
|
|
16
|
-
Requires-Dist: meshagent-openai~=0.
|
|
17
|
-
Requires-Dist: meshagent-tools~=0.
|
|
18
|
-
Requires-Dist: meshagent-mcp~=0.
|
|
13
|
+
Requires-Dist: meshagent-api~=0.2.0
|
|
14
|
+
Requires-Dist: meshagent-agents~=0.2.0
|
|
15
|
+
Requires-Dist: meshagent-computers~=0.2.0
|
|
16
|
+
Requires-Dist: meshagent-openai~=0.2.0
|
|
17
|
+
Requires-Dist: meshagent-tools~=0.2.0
|
|
18
|
+
Requires-Dist: meshagent-mcp~=0.2.0
|
|
19
19
|
Requires-Dist: supabase~=2.15
|
|
20
20
|
Requires-Dist: fastmcp~=2.8
|
|
21
21
|
Requires-Dist: opentelemetry-distro~=0.54b1
|
|
@@ -12,6 +12,7 @@ meshagent/cli/cli.py
|
|
|
12
12
|
meshagent/cli/cli_mcp.py
|
|
13
13
|
meshagent/cli/cli_secrets.py
|
|
14
14
|
meshagent/cli/developer.py
|
|
15
|
+
meshagent/cli/exec.py
|
|
15
16
|
meshagent/cli/helper.py
|
|
16
17
|
meshagent/cli/messaging.py
|
|
17
18
|
meshagent/cli/otel.py
|
|
@@ -20,7 +21,6 @@ meshagent/cli/projects.py
|
|
|
20
21
|
meshagent/cli/services.py
|
|
21
22
|
meshagent/cli/sessions.py
|
|
22
23
|
meshagent/cli/storage.py
|
|
23
|
-
meshagent/cli/tty.py
|
|
24
24
|
meshagent/cli/version.py
|
|
25
25
|
meshagent/cli/voicebot.py
|
|
26
26
|
meshagent/cli/webhook.py
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
typer~=0.15
|
|
2
2
|
pydantic-yaml~=1.4
|
|
3
|
-
meshagent-api~=0.
|
|
4
|
-
meshagent-agents~=0.
|
|
5
|
-
meshagent-computers~=0.
|
|
6
|
-
meshagent-openai~=0.
|
|
7
|
-
meshagent-tools~=0.
|
|
8
|
-
meshagent-mcp~=0.
|
|
3
|
+
meshagent-api~=0.2.0
|
|
4
|
+
meshagent-agents~=0.2.0
|
|
5
|
+
meshagent-computers~=0.2.0
|
|
6
|
+
meshagent-openai~=0.2.0
|
|
7
|
+
meshagent-tools~=0.2.0
|
|
8
|
+
meshagent-mcp~=0.2.0
|
|
9
9
|
supabase~=2.15
|
|
10
10
|
fastmcp~=2.8
|
|
11
11
|
opentelemetry-distro~=0.54b1
|
|
@@ -11,12 +11,12 @@ keywords = []
|
|
|
11
11
|
dependencies = [
|
|
12
12
|
"typer~=0.15",
|
|
13
13
|
"pydantic-yaml~=1.4",
|
|
14
|
-
"meshagent-api~=0.
|
|
15
|
-
"meshagent-agents~=0.
|
|
16
|
-
"meshagent-computers~=0.
|
|
17
|
-
"meshagent-openai~=0.
|
|
18
|
-
"meshagent-tools~=0.
|
|
19
|
-
"meshagent-mcp~=0.
|
|
14
|
+
"meshagent-api~=0.2.0",
|
|
15
|
+
"meshagent-agents~=0.2.0",
|
|
16
|
+
"meshagent-computers~=0.2.0",
|
|
17
|
+
"meshagent-openai~=0.2.0",
|
|
18
|
+
"meshagent-tools~=0.2.0",
|
|
19
|
+
"meshagent-mcp~=0.2.0",
|
|
20
20
|
"supabase~=2.15",
|
|
21
21
|
"fastmcp~=2.8",
|
|
22
22
|
"opentelemetry-distro~=0.54b1",
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import tty
|
|
3
|
-
import termios
|
|
4
|
-
from meshagent.api.helpers import websocket_room_url
|
|
5
|
-
from typing import Annotated, Optional
|
|
6
|
-
import os
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import typer
|
|
10
|
-
from rich import print
|
|
11
|
-
import aiohttp
|
|
12
|
-
|
|
13
|
-
from meshagent.api import ParticipantToken
|
|
14
|
-
from meshagent.cli import async_typer
|
|
15
|
-
from meshagent.cli.helper import (
|
|
16
|
-
get_client,
|
|
17
|
-
resolve_project_id,
|
|
18
|
-
resolve_api_key,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
app = async_typer.AsyncTyper()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@app.async_command("connect")
|
|
26
|
-
async def tty_command(
|
|
27
|
-
*,
|
|
28
|
-
project_id: str = None,
|
|
29
|
-
room: Annotated[str, typer.Option()],
|
|
30
|
-
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
31
|
-
):
|
|
32
|
-
"""Open an interactive websocket‑based TTY."""
|
|
33
|
-
client = await get_client()
|
|
34
|
-
try:
|
|
35
|
-
project_id = await resolve_project_id(project_id=project_id)
|
|
36
|
-
api_key_id = await resolve_api_key(project_id=project_id, api_key_id=api_key_id)
|
|
37
|
-
|
|
38
|
-
token = ParticipantToken(
|
|
39
|
-
name="tty", project_id=project_id, api_key_id=api_key_id
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
key = (
|
|
43
|
-
await client.decrypt_project_api_key(project_id=project_id, id=api_key_id)
|
|
44
|
-
)["token"]
|
|
45
|
-
|
|
46
|
-
token.add_role_grant(role="user")
|
|
47
|
-
token.add_room_grant(room)
|
|
48
|
-
|
|
49
|
-
ws_url = (
|
|
50
|
-
websocket_room_url(room_name=room) + f"/tty?token={token.to_jwt(token=key)}"
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
print(f"[bold green]Connecting to[/bold green] {room}")
|
|
54
|
-
|
|
55
|
-
# Save current terminal settings so we can restore them later.
|
|
56
|
-
old_tty_settings = termios.tcgetattr(sys.stdin)
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
async with aiohttp.ClientSession() as session:
|
|
60
|
-
async with session.ws_connect(ws_url) as websocket:
|
|
61
|
-
tty.setraw(sys.stdin)
|
|
62
|
-
|
|
63
|
-
loop = asyncio.get_running_loop()
|
|
64
|
-
transport, protocol = await loop.connect_write_pipe(
|
|
65
|
-
asyncio.streams.FlowControlMixin, sys.stdout
|
|
66
|
-
)
|
|
67
|
-
writer = asyncio.StreamWriter(transport, protocol, None, loop)
|
|
68
|
-
|
|
69
|
-
async def recv_from_websocket():
|
|
70
|
-
async for message in websocket:
|
|
71
|
-
if message.type == aiohttp.WSMsgType.CLOSE:
|
|
72
|
-
await websocket.close()
|
|
73
|
-
|
|
74
|
-
elif message.type == aiohttp.WSMsgType.ERROR:
|
|
75
|
-
await websocket.close()
|
|
76
|
-
|
|
77
|
-
data: bytes = message.data
|
|
78
|
-
writer.write(data)
|
|
79
|
-
await writer.drain()
|
|
80
|
-
|
|
81
|
-
async def send_to_websocket():
|
|
82
|
-
loop = asyncio.get_running_loop()
|
|
83
|
-
|
|
84
|
-
reader = asyncio.StreamReader()
|
|
85
|
-
protocol = asyncio.StreamReaderProtocol(reader)
|
|
86
|
-
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
|
|
87
|
-
|
|
88
|
-
while True:
|
|
89
|
-
# Read one character at a time from stdin without blocking the event loop.
|
|
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
|
-
[
|
|
110
|
-
asyncio.create_task(recv_from_websocket()),
|
|
111
|
-
asyncio.create_task(send_to_websocket()),
|
|
112
|
-
],
|
|
113
|
-
return_when=asyncio.FIRST_COMPLETED,
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
for task in pending:
|
|
117
|
-
task.cancel()
|
|
118
|
-
|
|
119
|
-
finally:
|
|
120
|
-
# Restore original terminal settings even if the coroutine is cancelled.
|
|
121
|
-
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty_settings)
|
|
122
|
-
|
|
123
|
-
finally:
|
|
124
|
-
await client.close()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|