meshagent-cli 0.0.17__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.17/PKG-INFO +23 -0
- meshagent_cli-0.0.17/README.md +7 -0
- meshagent_cli-0.0.17/meshagent/cli/__init__.py +0 -0
- meshagent_cli-0.0.17/meshagent/cli/agent.py +259 -0
- meshagent_cli-0.0.17/meshagent/cli/api_keys.py +74 -0
- meshagent_cli-0.0.17/meshagent/cli/async_typer.py +31 -0
- meshagent_cli-0.0.17/meshagent/cli/auth.py +28 -0
- meshagent_cli-0.0.17/meshagent/cli/auth_async.py +115 -0
- meshagent_cli-0.0.17/meshagent/cli/call.py +127 -0
- meshagent_cli-0.0.17/meshagent/cli/cli.py +35 -0
- meshagent_cli-0.0.17/meshagent/cli/cli_mcp.py +113 -0
- meshagent_cli-0.0.17/meshagent/cli/cli_secrets.py +383 -0
- meshagent_cli-0.0.17/meshagent/cli/developer.py +76 -0
- meshagent_cli-0.0.17/meshagent/cli/helper.py +113 -0
- meshagent_cli-0.0.17/meshagent/cli/messaging.py +192 -0
- meshagent_cli-0.0.17/meshagent/cli/participant_token.py +33 -0
- meshagent_cli-0.0.17/meshagent/cli/projects.py +31 -0
- meshagent_cli-0.0.17/meshagent/cli/services.py +177 -0
- meshagent_cli-0.0.17/meshagent/cli/sessions.py +19 -0
- meshagent_cli-0.0.17/meshagent/cli/storage.py +801 -0
- meshagent_cli-0.0.17/meshagent/cli/version.py +1 -0
- meshagent_cli-0.0.17/meshagent/cli/webhook.py +89 -0
- meshagent_cli-0.0.17/meshagent_cli.egg-info/PKG-INFO +23 -0
- meshagent_cli-0.0.17/meshagent_cli.egg-info/SOURCES.txt +28 -0
- meshagent_cli-0.0.17/meshagent_cli.egg-info/dependency_links.txt +1 -0
- meshagent_cli-0.0.17/meshagent_cli.egg-info/entry_points.txt +2 -0
- meshagent_cli-0.0.17/meshagent_cli.egg-info/requires.txt +5 -0
- meshagent_cli-0.0.17/meshagent_cli.egg-info/top_level.txt +1 -0
- meshagent_cli-0.0.17/pyproject.toml +36 -0
- meshagent_cli-0.0.17/setup.cfg +4 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meshagent-cli
|
|
3
|
+
Version: 0.0.17
|
|
4
|
+
Summary: CLI for Meshagent
|
|
5
|
+
License-Expression: LicenseRef-Proprietary
|
|
6
|
+
Project-URL: Documentation, https://meshagent.com
|
|
7
|
+
Project-URL: Website, https://meshagent.com
|
|
8
|
+
Project-URL: Source, https://github.com/meshagent
|
|
9
|
+
Requires-Python: >=3.9.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: typer~=0.15.3
|
|
12
|
+
Requires-Dist: pydantic-yaml~=1.4.0
|
|
13
|
+
Requires-Dist: meshagent-api~=0.0.17
|
|
14
|
+
Requires-Dist: meshagent-agents~=0.0.17
|
|
15
|
+
Requires-Dist: meshagent-tools~=0.0.17
|
|
16
|
+
|
|
17
|
+
### Meshagent CLI
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
File without changes
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
from meshagent.cli import async_typer
|
|
2
|
+
import typer
|
|
3
|
+
from meshagent.cli.helper import get_client, print_json_table, set_active_project, resolve_project_id
|
|
4
|
+
from rich import print
|
|
5
|
+
from meshagent.api import RoomClient, ParticipantToken, WebSocketClientProtocol, RoomException
|
|
6
|
+
from meshagent.cli.helper import set_active_project, get_active_project, resolve_project_id, resolve_api_key
|
|
7
|
+
from typing import Annotated, Optional
|
|
8
|
+
from meshagent.api.helpers import meshagent_base_url, websocket_room_url
|
|
9
|
+
import json
|
|
10
|
+
import asyncio
|
|
11
|
+
import aiohttp
|
|
12
|
+
from meshagent.api.services import send_webhook
|
|
13
|
+
|
|
14
|
+
app = async_typer.AsyncTyper()
|
|
15
|
+
|
|
16
|
+
@app.async_command("ask")
|
|
17
|
+
async def ask(*, project_id: str = None, room: Annotated[str, typer.Option()], api_key_id: Annotated[Optional[str], typer.Option()] = None, name: Annotated[str, typer.Option(..., help="Participant name")] = "cli", role: str = "user", agent: Annotated[str, typer.Option()], input: Annotated[str, typer.Option()], timeout: Annotated[Optional[int], typer.Option(..., help="How long to wait for the agent if the agent is not in the room")] = 30):
|
|
18
|
+
account_client = await get_client()
|
|
19
|
+
try:
|
|
20
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
21
|
+
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
22
|
+
|
|
23
|
+
key = (await account_client.decrypt_project_api_key(project_id=project_id, id=api_key_id))["token"]
|
|
24
|
+
|
|
25
|
+
token = ParticipantToken(
|
|
26
|
+
name=name,
|
|
27
|
+
project_id=project_id,
|
|
28
|
+
api_key_id=api_key_id
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
token.add_role_grant(role=role)
|
|
32
|
+
token.add_room_grant(room)
|
|
33
|
+
|
|
34
|
+
print("[bold green]Connecting to room...[/bold green]")
|
|
35
|
+
async with RoomClient(protocol=WebSocketClientProtocol(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()), token=token.to_jwt(token=key))) as client:
|
|
36
|
+
|
|
37
|
+
found = timeout == 0
|
|
38
|
+
for i in range(30):
|
|
39
|
+
if found:
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
if i == 1:
|
|
43
|
+
print("[magenta]Waiting for agent...[/magenta]")
|
|
44
|
+
|
|
45
|
+
agents = await client.agents.list_agents()
|
|
46
|
+
await asyncio.sleep(1)
|
|
47
|
+
|
|
48
|
+
for a in agents:
|
|
49
|
+
if a.name == agent:
|
|
50
|
+
found = True
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
if not found:
|
|
54
|
+
print("[red]Timed out waiting for agent to join the room[/red]")
|
|
55
|
+
raise typer.Exit(1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
print("[magenta]Asking agent...[/magenta]")
|
|
59
|
+
|
|
60
|
+
response = await client.agents.ask(agent=agent, arguments=json.loads(input))
|
|
61
|
+
print(json.dumps(response.json))
|
|
62
|
+
except RoomException as e:
|
|
63
|
+
print(f"[red]{e}[/red]")
|
|
64
|
+
finally:
|
|
65
|
+
await account_client.close()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.async_command("invoke-tool")
|
|
70
|
+
async def invoke_tool(
|
|
71
|
+
*,
|
|
72
|
+
project_id: str = None,
|
|
73
|
+
room: Annotated[str, typer.Option()],
|
|
74
|
+
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
75
|
+
name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
|
|
76
|
+
role: str = "user",
|
|
77
|
+
toolkit: Annotated[str, typer.Option(..., help="Toolkit name")],
|
|
78
|
+
tool: Annotated[str, typer.Option(..., help="Tool name")],
|
|
79
|
+
arguments: Annotated[str, typer.Option(..., help="JSON string with arguments for the tool")],
|
|
80
|
+
participant_id: Annotated[Optional[str], typer.Option(..., help="Optional participant ID to invoke the tool on")] = None,
|
|
81
|
+
on_behalf_of_id: Annotated[Optional[str], typer.Option(..., help="Optional 'on_behalf_of' participant ID")] = None,
|
|
82
|
+
caller_context: Annotated[Optional[str], typer.Option(..., help="Optional JSON for caller context")] = None,
|
|
83
|
+
timeout: Annotated[Optional[int], typer.Option(..., help="How long to wait for the toolkit if the toolkit is not in the room")] = 30,
|
|
84
|
+
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Invoke a specific tool from a given toolkit with arguments.
|
|
88
|
+
"""
|
|
89
|
+
account_client = await get_client()
|
|
90
|
+
try:
|
|
91
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
92
|
+
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
93
|
+
key = (await account_client.decrypt_project_api_key(project_id=project_id, id=api_key_id))["token"]
|
|
94
|
+
|
|
95
|
+
token = ParticipantToken(
|
|
96
|
+
name=name,
|
|
97
|
+
project_id=project_id,
|
|
98
|
+
api_key_id=api_key_id
|
|
99
|
+
)
|
|
100
|
+
token.add_role_grant(role=role)
|
|
101
|
+
token.add_room_grant(room)
|
|
102
|
+
|
|
103
|
+
print("[bold green]Connecting to room...[/bold green]")
|
|
104
|
+
async with RoomClient(
|
|
105
|
+
protocol=WebSocketClientProtocol(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
106
|
+
token=token.to_jwt(token=key))
|
|
107
|
+
) as client:
|
|
108
|
+
|
|
109
|
+
found = timeout == 0
|
|
110
|
+
for i in range(timeout):
|
|
111
|
+
if found:
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
if i == 1:
|
|
115
|
+
print("[magenta]Waiting for toolkit...[/magenta]")
|
|
116
|
+
|
|
117
|
+
agents = await client.agents.list_toolkits(participant_id=participant_id)
|
|
118
|
+
await asyncio.sleep(1)
|
|
119
|
+
|
|
120
|
+
for a in agents:
|
|
121
|
+
if a.name == toolkit:
|
|
122
|
+
found = True
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
if not found:
|
|
126
|
+
print("[red]Timed out waiting for toolkit to join the room[/red]")
|
|
127
|
+
raise typer.Exit(1)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
print("[bold green]Invoking tool...[/bold green]")
|
|
131
|
+
parsed_context = json.loads(caller_context) if caller_context else None
|
|
132
|
+
response = await client.agents.invoke_tool(
|
|
133
|
+
toolkit=toolkit,
|
|
134
|
+
tool=tool,
|
|
135
|
+
arguments=json.loads(arguments),
|
|
136
|
+
participant_id=participant_id,
|
|
137
|
+
on_behalf_of_id=on_behalf_of_id,
|
|
138
|
+
caller_context=parsed_context,
|
|
139
|
+
)
|
|
140
|
+
# The response is presumably a dictionary or similar
|
|
141
|
+
print(response.to_json())
|
|
142
|
+
except RoomException as e:
|
|
143
|
+
print(f"[red]{e}[/red]")
|
|
144
|
+
finally:
|
|
145
|
+
await account_client.close()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@app.async_command("list-agents")
|
|
149
|
+
async def list_agents_command(
|
|
150
|
+
*,
|
|
151
|
+
project_id: str = None,
|
|
152
|
+
room: Annotated[str, typer.Option()],
|
|
153
|
+
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
154
|
+
name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
|
|
155
|
+
role: str = "user"
|
|
156
|
+
):
|
|
157
|
+
"""
|
|
158
|
+
List all agents available in the room.
|
|
159
|
+
"""
|
|
160
|
+
account_client = await get_client()
|
|
161
|
+
try:
|
|
162
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
163
|
+
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
164
|
+
key = (await account_client.decrypt_project_api_key(project_id=project_id, id=api_key_id))["token"]
|
|
165
|
+
|
|
166
|
+
token = ParticipantToken(
|
|
167
|
+
name=name,
|
|
168
|
+
project_id=project_id,
|
|
169
|
+
api_key_id=api_key_id
|
|
170
|
+
)
|
|
171
|
+
token.add_role_grant(role=role)
|
|
172
|
+
token.add_room_grant(room)
|
|
173
|
+
|
|
174
|
+
print("[bold green]Connecting to room...[/bold green]")
|
|
175
|
+
async with RoomClient(
|
|
176
|
+
protocol=WebSocketClientProtocol(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
177
|
+
token=token.to_jwt(token=key))
|
|
178
|
+
) as client:
|
|
179
|
+
print("[bold green]Fetching list of agents...[/bold green]")
|
|
180
|
+
agents = await client.agents.list_agents()
|
|
181
|
+
# Format the output as JSON
|
|
182
|
+
output = []
|
|
183
|
+
for agent in agents:
|
|
184
|
+
output.append({
|
|
185
|
+
"name": agent.name,
|
|
186
|
+
"title": agent.title,
|
|
187
|
+
"description": agent.description,
|
|
188
|
+
"requires": [r.to_json() for r in agent.requires],
|
|
189
|
+
"supports_tools": agent.supports_tools,
|
|
190
|
+
"labels": agent.labels,
|
|
191
|
+
})
|
|
192
|
+
print(json.dumps(output, indent=2))
|
|
193
|
+
|
|
194
|
+
finally:
|
|
195
|
+
await account_client.close()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@app.async_command("list-toolkits")
|
|
199
|
+
async def list_toolkits_command(
|
|
200
|
+
*,
|
|
201
|
+
project_id: str = None,
|
|
202
|
+
room: Annotated[str, typer.Option()],
|
|
203
|
+
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
204
|
+
name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
|
|
205
|
+
role: str = "user",
|
|
206
|
+
participant_id: Annotated[Optional[str], typer.Option(..., help="Optional participant ID")] = None
|
|
207
|
+
):
|
|
208
|
+
"""
|
|
209
|
+
List all toolkits (and tools within them) available in the room.
|
|
210
|
+
"""
|
|
211
|
+
account_client = await get_client()
|
|
212
|
+
try:
|
|
213
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
214
|
+
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
215
|
+
key = (await account_client.decrypt_project_api_key(project_id=project_id, id=api_key_id))["token"]
|
|
216
|
+
|
|
217
|
+
token = ParticipantToken(
|
|
218
|
+
name=name,
|
|
219
|
+
project_id=project_id,
|
|
220
|
+
api_key_id=api_key_id
|
|
221
|
+
)
|
|
222
|
+
token.add_role_grant(role=role)
|
|
223
|
+
token.add_room_grant(room)
|
|
224
|
+
|
|
225
|
+
print("[bold green]Connecting to room...[/bold green]")
|
|
226
|
+
async with RoomClient(
|
|
227
|
+
protocol=WebSocketClientProtocol(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
228
|
+
token=token.to_jwt(token=key))
|
|
229
|
+
) as client:
|
|
230
|
+
print("[bold green]Fetching list of toolkits...[/bold green]")
|
|
231
|
+
toolkits = await client.agents.list_toolkits(participant_id=participant_id)
|
|
232
|
+
|
|
233
|
+
# Format and output as JSON
|
|
234
|
+
output = []
|
|
235
|
+
for tk in toolkits:
|
|
236
|
+
output.append({
|
|
237
|
+
"name": tk.name,
|
|
238
|
+
"title": tk.title,
|
|
239
|
+
"description": tk.description,
|
|
240
|
+
"thumbnail_url": tk.thumbnail_url,
|
|
241
|
+
"tools": [
|
|
242
|
+
{
|
|
243
|
+
"name": tool.name,
|
|
244
|
+
"title": tool.title,
|
|
245
|
+
"description": tool.description,
|
|
246
|
+
"input_schema": tool.input_schema,
|
|
247
|
+
"thumbnail_url": tool.thumbnail_url,
|
|
248
|
+
"defs": tool.defs,
|
|
249
|
+
"supports_context": tool.supports_context
|
|
250
|
+
}
|
|
251
|
+
for tool in tk.tools
|
|
252
|
+
]
|
|
253
|
+
})
|
|
254
|
+
print(json.dumps(output, indent=2))
|
|
255
|
+
|
|
256
|
+
finally:
|
|
257
|
+
await account_client.close()
|
|
258
|
+
|
|
259
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from meshagent.cli import async_typer
|
|
2
|
+
import typer
|
|
3
|
+
from meshagent.cli.helper import get_client, print_json_table
|
|
4
|
+
from rich import print
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from meshagent.cli.helper import set_active_project, get_active_project, resolve_project_id, set_active_api_key, resolve_api_key
|
|
7
|
+
|
|
8
|
+
app = async_typer.AsyncTyper()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.async_command("list")
|
|
12
|
+
async def list(*, project_id: str = None):
|
|
13
|
+
|
|
14
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
15
|
+
|
|
16
|
+
client = await get_client()
|
|
17
|
+
keys = (await client.list_project_api_keys(project_id=project_id))["keys"]
|
|
18
|
+
if len(keys) > 0:
|
|
19
|
+
print_json_table(keys, "id", "name", "description")
|
|
20
|
+
else:
|
|
21
|
+
print("There are not currently any API keys in the project")
|
|
22
|
+
await client.close()
|
|
23
|
+
|
|
24
|
+
@app.async_command("create")
|
|
25
|
+
async def create(*, project_id: str = None, name: str, description: str = ""):
|
|
26
|
+
|
|
27
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
28
|
+
|
|
29
|
+
client = await get_client()
|
|
30
|
+
api_key = await client.create_project_api_key(project_id=project_id, name=name, description=description)
|
|
31
|
+
print(api_key["token"])
|
|
32
|
+
await client.close()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.async_command("delete")
|
|
36
|
+
async def delete(*, project_id: str = None, id: str):
|
|
37
|
+
|
|
38
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
39
|
+
|
|
40
|
+
client = await get_client()
|
|
41
|
+
await client.delete_project_api_key(project_id=project_id, id=id)
|
|
42
|
+
await client.close()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.async_command("show")
|
|
46
|
+
async def show(*, project_id: str = None, api_key_id: str):
|
|
47
|
+
client = await get_client()
|
|
48
|
+
try:
|
|
49
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
50
|
+
|
|
51
|
+
key = await client.decrypt_project_api_key(project_id=project_id, id=api_key_id)
|
|
52
|
+
|
|
53
|
+
print(key["token"])
|
|
54
|
+
|
|
55
|
+
finally:
|
|
56
|
+
await client.close()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.async_command("activate")
|
|
60
|
+
async def activate(api_key_id: str, project_id: str = None,):
|
|
61
|
+
client = await get_client()
|
|
62
|
+
try:
|
|
63
|
+
project_id = await resolve_project_id(project_id)
|
|
64
|
+
response = await client.list_project_api_keys(project_id=project_id)
|
|
65
|
+
api_keys = response["keys"]
|
|
66
|
+
for api_key in api_keys:
|
|
67
|
+
if api_key["id"] == api_key_id:
|
|
68
|
+
await set_active_api_key(project_id=project_id, api_key_id=api_key_id)
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
print(f"[red]Invalid api key id or project id: {project_id}[/red]")
|
|
72
|
+
raise typer.Exit(code=1)
|
|
73
|
+
finally:
|
|
74
|
+
await client.close()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
5
|
+
from functools import partial, wraps
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from typer import Typer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncTyper(Typer):
|
|
12
|
+
@staticmethod
|
|
13
|
+
def maybe_run_async(decorator: Callable, func: Callable) -> Any:
|
|
14
|
+
if inspect.iscoroutinefunction(func):
|
|
15
|
+
|
|
16
|
+
@wraps(func)
|
|
17
|
+
def runner(*args: Any, **kwargs: Any) -> Any:
|
|
18
|
+
return asyncio.run(func(*args, **kwargs))
|
|
19
|
+
|
|
20
|
+
decorator(runner)
|
|
21
|
+
else:
|
|
22
|
+
decorator(func)
|
|
23
|
+
return func
|
|
24
|
+
|
|
25
|
+
def callback(self, *args: Any, **kwargs: Any) -> Any:
|
|
26
|
+
decorator = super().callback(*args, **kwargs)
|
|
27
|
+
return partial(self.maybe_run_async, decorator)
|
|
28
|
+
|
|
29
|
+
def async_command(self, *args: Any, **kwargs: Any) -> Any:
|
|
30
|
+
decorator = super().command(*args, **kwargs)
|
|
31
|
+
return partial(self.maybe_run_async, decorator)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from meshagent.api.accounts_client import AccountsClient
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from .helper import set_active_project, get_active_project
|
|
6
|
+
|
|
7
|
+
from meshagent.cli import async_typer
|
|
8
|
+
from meshagent.cli import auth_async
|
|
9
|
+
|
|
10
|
+
app = async_typer.AsyncTyper()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.async_command("login")
|
|
14
|
+
async def login():
|
|
15
|
+
await auth_async.login()
|
|
16
|
+
|
|
17
|
+
project_id = await get_active_project()
|
|
18
|
+
if project_id == None:
|
|
19
|
+
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"')
|
|
20
|
+
|
|
21
|
+
@app.async_command("logout")
|
|
22
|
+
async def login():
|
|
23
|
+
await auth_async.logout()
|
|
24
|
+
|
|
25
|
+
@app.async_command("whoami")
|
|
26
|
+
async def whoami():
|
|
27
|
+
_, s = await auth_async.session()
|
|
28
|
+
typer.echo(s.user.email if s else "Not logged in")
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import os, json, webbrowser, asyncio
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from aiohttp import web
|
|
4
|
+
from supabase._async.client import AsyncClient, create_client # async flavour :contentReference[oaicite:1]{index=1}
|
|
5
|
+
from supabase.lib.client_options import ClientOptions
|
|
6
|
+
from gotrue import AsyncMemoryStorage
|
|
7
|
+
|
|
8
|
+
AUTH_URL = os.getenv("MESHAGENT_AUTH_URL", "https://infra.meshagent.com")
|
|
9
|
+
AUTH_ANON_KEY = os.getenv("MESHAGENT_AUTH_ANON_KEY", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZqZGh5bWhhZ3BwZ2drYWJ6bGFmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzYzMTYyOTQsImV4cCI6MjA1MTg5MjI5NH0.8AuzzNcCuEaHQ-gjqHxBmsN1YrtM-TpL1_W-kxzooNs")
|
|
10
|
+
CACHE_FILE = Path.home() / ".meshagent" / "session.json"
|
|
11
|
+
REDIRECT_PORT = 8765
|
|
12
|
+
REDIRECT_URL = f"http://localhost:{REDIRECT_PORT}/callback"
|
|
13
|
+
|
|
14
|
+
# ---------- helpers ----------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
def _ensure_cache_dir():
|
|
17
|
+
CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
async def _client() -> AsyncClient:
|
|
20
|
+
return await create_client(
|
|
21
|
+
AUTH_URL,
|
|
22
|
+
AUTH_ANON_KEY,
|
|
23
|
+
options=ClientOptions(
|
|
24
|
+
flow_type="pkce", # OAuth + PKCE :contentReference[oaicite:2]{index=2}
|
|
25
|
+
auto_refresh_token=True,
|
|
26
|
+
persist_session=False,
|
|
27
|
+
storage=AsyncMemoryStorage()
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def _save(s):
|
|
32
|
+
_ensure_cache_dir()
|
|
33
|
+
CACHE_FILE.write_text(json.dumps({
|
|
34
|
+
"access_token": s.access_token,
|
|
35
|
+
"refresh_token": s.refresh_token,
|
|
36
|
+
"expires_at": s.expires_at, # int (seconds since epoch)
|
|
37
|
+
}))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _load():
|
|
41
|
+
_ensure_cache_dir()
|
|
42
|
+
if CACHE_FILE.exists():
|
|
43
|
+
return json.loads(CACHE_FILE.read_text())
|
|
44
|
+
|
|
45
|
+
# ---------- local HTTP callback ---------------------------------------------
|
|
46
|
+
|
|
47
|
+
async def _wait_for_code() -> str:
|
|
48
|
+
"""Spin up a one-shot aiohttp server and await ?code=…"""
|
|
49
|
+
app = web.Application()
|
|
50
|
+
code_fut: asyncio.Future[str] = asyncio.get_event_loop().create_future()
|
|
51
|
+
|
|
52
|
+
async def callback(request):
|
|
53
|
+
code = request.query.get("code")
|
|
54
|
+
if code:
|
|
55
|
+
if not code_fut.done():
|
|
56
|
+
code_fut.set_result(code)
|
|
57
|
+
return web.Response(text="You may close this tab.")
|
|
58
|
+
return web.Response(status=400)
|
|
59
|
+
|
|
60
|
+
app.add_routes([web.get("/callback", callback)])
|
|
61
|
+
runner = web.AppRunner(app)
|
|
62
|
+
await runner.setup()
|
|
63
|
+
site = web.TCPSite(runner, "localhost", REDIRECT_PORT)
|
|
64
|
+
await site.start()
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
return await code_fut
|
|
68
|
+
finally:
|
|
69
|
+
await runner.cleanup()
|
|
70
|
+
|
|
71
|
+
# ---------- public API -------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
async def login():
|
|
74
|
+
supa = await _client()
|
|
75
|
+
|
|
76
|
+
# 1️⃣ Build provider URL – async now
|
|
77
|
+
res = await supa.auth.sign_in_with_oauth(
|
|
78
|
+
{
|
|
79
|
+
"provider": "google",
|
|
80
|
+
"options": {"redirect_to": REDIRECT_URL},
|
|
81
|
+
}
|
|
82
|
+
) # :contentReference[oaicite:3]{index=3}
|
|
83
|
+
oauth_url = res.url
|
|
84
|
+
|
|
85
|
+
# 2️⃣ Kick user to browser without blocking the loop
|
|
86
|
+
await asyncio.to_thread(webbrowser.open, oauth_url)
|
|
87
|
+
print(f"Waiting for Google OAuth redirect on {oauth_url}…")
|
|
88
|
+
|
|
89
|
+
# 3️⃣ Await the auth code, then exchange for tokens
|
|
90
|
+
auth_code = await _wait_for_code()
|
|
91
|
+
print("Got code, exchanging…")
|
|
92
|
+
sess = await supa.auth.exchange_code_for_session({"auth_code": auth_code}) #
|
|
93
|
+
_save(sess.session)
|
|
94
|
+
print("✅ Logged in as", sess.user.email)
|
|
95
|
+
|
|
96
|
+
async def session():
|
|
97
|
+
supa = await _client()
|
|
98
|
+
cached = _load()
|
|
99
|
+
fresh = None
|
|
100
|
+
if cached:
|
|
101
|
+
await supa.auth.set_session(cached["access_token"], cached["refresh_token"])
|
|
102
|
+
fresh = await supa.auth.get_session() # returns a Session object
|
|
103
|
+
_save(fresh)
|
|
104
|
+
return supa, fresh
|
|
105
|
+
|
|
106
|
+
async def logout():
|
|
107
|
+
supa, s = await session()
|
|
108
|
+
if s:
|
|
109
|
+
await supa.auth.sign_out()
|
|
110
|
+
CACHE_FILE.unlink(missing_ok=True)
|
|
111
|
+
print("👋 Signed out")
|
|
112
|
+
|
|
113
|
+
async def get_access_token():
|
|
114
|
+
supa, fresh = await session()
|
|
115
|
+
return fresh.access_token
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
|
|
2
|
+
from meshagent.cli import async_typer
|
|
3
|
+
import typer
|
|
4
|
+
from meshagent.cli.helper import get_client, print_json_table, set_active_project, resolve_project_id
|
|
5
|
+
from rich import print
|
|
6
|
+
from meshagent.api import RoomClient, ParticipantToken, WebSocketClientProtocol, RoomException
|
|
7
|
+
from meshagent.cli.helper import set_active_project, get_active_project, resolve_project_id, resolve_api_key
|
|
8
|
+
from typing import Annotated, Optional
|
|
9
|
+
from meshagent.api.helpers import meshagent_base_url, websocket_room_url
|
|
10
|
+
import json
|
|
11
|
+
import aiohttp
|
|
12
|
+
from meshagent.api.services import send_webhook
|
|
13
|
+
|
|
14
|
+
app = async_typer.AsyncTyper()
|
|
15
|
+
|
|
16
|
+
from urllib.parse import urlparse
|
|
17
|
+
from pathlib import PurePath
|
|
18
|
+
import socket
|
|
19
|
+
import ipaddress
|
|
20
|
+
|
|
21
|
+
PRIVATE_NETS = (
|
|
22
|
+
ipaddress.ip_network("10.0.0.0/8"),
|
|
23
|
+
ipaddress.ip_network("172.16.0.0/12"),
|
|
24
|
+
ipaddress.ip_network("192.168.0.0/16"),
|
|
25
|
+
ipaddress.ip_network("169.254.0.0/16"), # IPv4 link-local
|
|
26
|
+
ipaddress.ip_network("fc00::/7"), # IPv6 unique-local
|
|
27
|
+
ipaddress.ip_network("fe80::/10"), # IPv6 link-local
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_local_url(url: str) -> bool:
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
Return True if *url* points to the local machine or a private-LAN host.
|
|
35
|
+
"""
|
|
36
|
+
# 1. Handle bare paths and file://
|
|
37
|
+
if "://" not in url:
|
|
38
|
+
return PurePath(url).is_absolute() or not ("/" in url or "\\" in url)
|
|
39
|
+
parsed = urlparse(url)
|
|
40
|
+
if parsed.scheme == "file":
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
# 2. Quick loop-back check on hostname literal
|
|
44
|
+
hostname = parsed.hostname
|
|
45
|
+
if hostname in {"localhost", None}: # None ⇒ something like "http:///path"
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Accept both direct IP literals and DNS names
|
|
50
|
+
addr_info = socket.getaddrinfo(hostname, None)
|
|
51
|
+
except socket.gaierror:
|
|
52
|
+
return False # Unresolvable host ⇒ treat as non-local (or raise)
|
|
53
|
+
|
|
54
|
+
for *_, sockaddr in addr_info:
|
|
55
|
+
ip_str = sockaddr[0]
|
|
56
|
+
ip = ipaddress.ip_address(ip_str)
|
|
57
|
+
|
|
58
|
+
if ip.is_loopback:
|
|
59
|
+
return True
|
|
60
|
+
if any(ip in net for net in PRIVATE_NETS):
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.async_command("schema")
|
|
65
|
+
@app.async_command("tool")
|
|
66
|
+
@app.async_command("agent")
|
|
67
|
+
async def make_call(
|
|
68
|
+
*,
|
|
69
|
+
project_id: str = None,
|
|
70
|
+
room: Annotated[str, typer.Option()],
|
|
71
|
+
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
72
|
+
name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
|
|
73
|
+
role: str = "agent",
|
|
74
|
+
local: Optional[bool] = None,
|
|
75
|
+
agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
|
|
76
|
+
url: Annotated[str, typer.Option(..., help="URL the agent should call")],
|
|
77
|
+
arguments: Annotated[str, typer.Option(..., help="JSON string with arguments for the call")] = {}
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Instruct an agent to 'call' a given URL with specific arguments.
|
|
81
|
+
"""
|
|
82
|
+
account_client = await get_client()
|
|
83
|
+
try:
|
|
84
|
+
project_id = await resolve_project_id(project_id=project_id)
|
|
85
|
+
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
86
|
+
|
|
87
|
+
key = (await account_client.decrypt_project_api_key(project_id=project_id, id=api_key_id))["token"]
|
|
88
|
+
|
|
89
|
+
token = ParticipantToken(
|
|
90
|
+
name=name,
|
|
91
|
+
project_id=project_id,
|
|
92
|
+
api_key_id=api_key_id
|
|
93
|
+
)
|
|
94
|
+
token.add_role_grant(role=role)
|
|
95
|
+
token.add_room_grant(room)
|
|
96
|
+
|
|
97
|
+
if local == None:
|
|
98
|
+
local = is_local_url(url)
|
|
99
|
+
|
|
100
|
+
if local:
|
|
101
|
+
async with aiohttp.ClientSession() as session:
|
|
102
|
+
|
|
103
|
+
event="room.call"
|
|
104
|
+
data={
|
|
105
|
+
"room_url" : websocket_room_url(room_name=room),
|
|
106
|
+
"room_name" : room,
|
|
107
|
+
"token" : token.to_jwt(token=key),
|
|
108
|
+
"arguments" : arguments,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await send_webhook(session=session, url=url, event=event, data=data, secret=None)
|
|
112
|
+
else:
|
|
113
|
+
print("[bold green]Connecting to room...[/bold green]")
|
|
114
|
+
async with RoomClient(
|
|
115
|
+
protocol=WebSocketClientProtocol(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
116
|
+
token=token.to_jwt(token=key))
|
|
117
|
+
) as client:
|
|
118
|
+
print("[bold green]Making agent call...[/bold green]")
|
|
119
|
+
await client.agents.make_call(
|
|
120
|
+
name=agent_name,
|
|
121
|
+
url=url,
|
|
122
|
+
arguments=json.loads(arguments)
|
|
123
|
+
)
|
|
124
|
+
print("[bold cyan]Call request sent successfully.[/bold cyan]")
|
|
125
|
+
|
|
126
|
+
finally:
|
|
127
|
+
await account_client.close()
|