meshagent-cli 0.0.37__py3-none-any.whl → 0.0.38__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.
Potentially problematic release.
This version of meshagent-cli might be problematic. Click here for more details.
- meshagent/cli/__init__.py +3 -1
- meshagent/cli/agent.py +139 -70
- meshagent/cli/api_keys.py +27 -9
- meshagent/cli/auth.py +9 -4
- meshagent/cli/auth_async.py +40 -17
- meshagent/cli/call.py +45 -36
- meshagent/cli/chatbot.py +126 -68
- meshagent/cli/cli.py +32 -19
- meshagent/cli/cli_mcp.py +183 -79
- meshagent/cli/cli_secrets.py +50 -20
- meshagent/cli/developer.py +19 -9
- meshagent/cli/helper.py +59 -39
- meshagent/cli/messaging.py +54 -31
- meshagent/cli/otel.py +36 -32
- meshagent/cli/participant_token.py +21 -12
- meshagent/cli/projects.py +3 -2
- meshagent/cli/services.py +50 -29
- meshagent/cli/sessions.py +9 -3
- meshagent/cli/storage.py +222 -94
- meshagent/cli/tty.py +24 -28
- meshagent/cli/version.py +1 -1
- meshagent/cli/voicebot.py +57 -35
- meshagent/cli/webhook.py +14 -5
- meshagent_cli-0.0.38.dist-info/METADATA +35 -0
- meshagent_cli-0.0.38.dist-info/RECORD +29 -0
- meshagent_cli-0.0.37.dist-info/METADATA +0 -28
- meshagent_cli-0.0.37.dist-info/RECORD +0 -29
- {meshagent_cli-0.0.37.dist-info → meshagent_cli-0.0.38.dist-info}/WHEEL +0 -0
- {meshagent_cli-0.0.37.dist-info → meshagent_cli-0.0.38.dist-info}/entry_points.txt +0 -0
- {meshagent_cli-0.0.37.dist-info → meshagent_cli-0.0.38.dist-info}/top_level.txt +0 -0
meshagent/cli/__init__.py
CHANGED
meshagent/cli/agent.py
CHANGED
|
@@ -5,35 +5,61 @@ import json
|
|
|
5
5
|
import asyncio
|
|
6
6
|
|
|
7
7
|
from meshagent.api.helpers import meshagent_base_url, websocket_room_url
|
|
8
|
-
from meshagent.api
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
from meshagent.api import (
|
|
9
|
+
RoomClient,
|
|
10
|
+
ParticipantToken,
|
|
11
|
+
WebSocketClientProtocol,
|
|
12
|
+
RoomException,
|
|
13
|
+
)
|
|
14
|
+
from meshagent.cli.helper import resolve_project_id, resolve_api_key
|
|
11
15
|
from meshagent.cli import async_typer
|
|
12
|
-
from meshagent.cli.helper import get_client,
|
|
16
|
+
from meshagent.cli.helper import get_client, resolve_token_jwt
|
|
13
17
|
|
|
14
18
|
app = async_typer.AsyncTyper()
|
|
15
19
|
|
|
20
|
+
|
|
16
21
|
@app.async_command("ask")
|
|
17
|
-
async def ask(
|
|
22
|
+
async def ask(
|
|
23
|
+
*,
|
|
24
|
+
project_id: str = None,
|
|
25
|
+
room: Annotated[str, typer.Option()],
|
|
26
|
+
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
27
|
+
name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
|
|
28
|
+
role: str = "user",
|
|
29
|
+
agent: Annotated[str, typer.Option()],
|
|
30
|
+
input: Annotated[str, typer.Option()],
|
|
31
|
+
timeout: Annotated[
|
|
32
|
+
Optional[int],
|
|
33
|
+
typer.Option(
|
|
34
|
+
..., help="How long to wait for the agent if the agent is not in the room"
|
|
35
|
+
),
|
|
36
|
+
] = 30,
|
|
37
|
+
):
|
|
18
38
|
account_client = await get_client()
|
|
19
39
|
try:
|
|
20
40
|
project_id = await resolve_project_id(project_id=project_id)
|
|
21
41
|
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
22
42
|
|
|
23
|
-
key = (
|
|
43
|
+
key = (
|
|
44
|
+
await account_client.decrypt_project_api_key(
|
|
45
|
+
project_id=project_id, id=api_key_id
|
|
46
|
+
)
|
|
47
|
+
)["token"]
|
|
24
48
|
|
|
25
49
|
token = ParticipantToken(
|
|
26
|
-
name=name,
|
|
27
|
-
project_id=project_id,
|
|
28
|
-
api_key_id=api_key_id
|
|
50
|
+
name=name, project_id=project_id, api_key_id=api_key_id
|
|
29
51
|
)
|
|
30
52
|
|
|
31
53
|
token.add_role_grant(role=role)
|
|
32
54
|
token.add_room_grant(room)
|
|
33
55
|
|
|
34
56
|
print("[bold green]Connecting to room...[/bold green]")
|
|
35
|
-
async with RoomClient(
|
|
36
|
-
|
|
57
|
+
async with RoomClient(
|
|
58
|
+
protocol=WebSocketClientProtocol(
|
|
59
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
60
|
+
token=token.to_jwt(token=key),
|
|
61
|
+
)
|
|
62
|
+
) as client:
|
|
37
63
|
found = timeout == 0
|
|
38
64
|
for i in range(30):
|
|
39
65
|
if found:
|
|
@@ -49,14 +75,13 @@ async def ask(*, project_id: str = None, room: Annotated[str, typer.Option()], a
|
|
|
49
75
|
if a.name == agent:
|
|
50
76
|
found = True
|
|
51
77
|
break
|
|
52
|
-
|
|
78
|
+
|
|
53
79
|
if not found:
|
|
54
80
|
print("[red]Timed out waiting for agent to join the room[/red]")
|
|
55
81
|
raise typer.Exit(1)
|
|
56
82
|
|
|
57
|
-
|
|
58
83
|
print("[magenta]Asking agent...[/magenta]")
|
|
59
|
-
|
|
84
|
+
|
|
60
85
|
response = await client.agents.ask(agent=agent, arguments=json.loads(input))
|
|
61
86
|
print(json.dumps(response.json))
|
|
62
87
|
except RoomException as e:
|
|
@@ -65,24 +90,37 @@ async def ask(*, project_id: str = None, room: Annotated[str, typer.Option()], a
|
|
|
65
90
|
await account_client.close()
|
|
66
91
|
|
|
67
92
|
|
|
68
|
-
|
|
69
93
|
@app.async_command("invoke-tool")
|
|
70
94
|
async def invoke_tool(
|
|
71
95
|
*,
|
|
72
96
|
project_id: str = None,
|
|
73
97
|
room: Annotated[str, typer.Option()],
|
|
74
|
-
token_path: Annotated[Optional[str], typer.Option()] = None,
|
|
98
|
+
token_path: Annotated[Optional[str], typer.Option()] = None,
|
|
75
99
|
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
76
100
|
name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
|
|
77
101
|
role: str = "user",
|
|
78
102
|
toolkit: Annotated[str, typer.Option(..., help="Toolkit name")],
|
|
79
103
|
tool: Annotated[str, typer.Option(..., help="Tool name")],
|
|
80
|
-
arguments: Annotated[
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
arguments: Annotated[
|
|
105
|
+
str, typer.Option(..., help="JSON string with arguments for the tool")
|
|
106
|
+
],
|
|
107
|
+
participant_id: Annotated[
|
|
108
|
+
Optional[str],
|
|
109
|
+
typer.Option(..., help="Optional participant ID to invoke the tool on"),
|
|
110
|
+
] = None,
|
|
111
|
+
on_behalf_of_id: Annotated[
|
|
112
|
+
Optional[str], typer.Option(..., help="Optional 'on_behalf_of' participant ID")
|
|
113
|
+
] = None,
|
|
114
|
+
caller_context: Annotated[
|
|
115
|
+
Optional[str], typer.Option(..., help="Optional JSON for caller context")
|
|
116
|
+
] = None,
|
|
117
|
+
timeout: Annotated[
|
|
118
|
+
Optional[int],
|
|
119
|
+
typer.Option(
|
|
120
|
+
...,
|
|
121
|
+
help="How long to wait for the toolkit if the toolkit is not in the room",
|
|
122
|
+
),
|
|
123
|
+
] = 30,
|
|
86
124
|
):
|
|
87
125
|
"""
|
|
88
126
|
Invoke a specific tool from a given toolkit with arguments.
|
|
@@ -92,14 +130,22 @@ async def invoke_tool(
|
|
|
92
130
|
project_id = await resolve_project_id(project_id=project_id)
|
|
93
131
|
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
94
132
|
|
|
95
|
-
jwt = await resolve_token_jwt(
|
|
133
|
+
jwt = await resolve_token_jwt(
|
|
134
|
+
project_id=project_id,
|
|
135
|
+
api_key_id=api_key_id,
|
|
136
|
+
token_path=token_path,
|
|
137
|
+
name=name,
|
|
138
|
+
role=role,
|
|
139
|
+
room=room,
|
|
140
|
+
)
|
|
96
141
|
|
|
97
142
|
print("[bold green]Connecting to room...[/bold green]")
|
|
98
143
|
async with RoomClient(
|
|
99
|
-
protocol=WebSocketClientProtocol(
|
|
100
|
-
|
|
144
|
+
protocol=WebSocketClientProtocol(
|
|
145
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
146
|
+
token=jwt,
|
|
147
|
+
)
|
|
101
148
|
) as client:
|
|
102
|
-
|
|
103
149
|
found = timeout == 0
|
|
104
150
|
for i in range(timeout):
|
|
105
151
|
if found:
|
|
@@ -108,19 +154,20 @@ async def invoke_tool(
|
|
|
108
154
|
if i == 1:
|
|
109
155
|
print("[magenta]Waiting for toolkit...[/magenta]")
|
|
110
156
|
|
|
111
|
-
agents = await client.agents.list_toolkits(
|
|
157
|
+
agents = await client.agents.list_toolkits(
|
|
158
|
+
participant_id=participant_id
|
|
159
|
+
)
|
|
112
160
|
await asyncio.sleep(1)
|
|
113
161
|
|
|
114
162
|
for a in agents:
|
|
115
163
|
if a.name == toolkit:
|
|
116
164
|
found = True
|
|
117
165
|
break
|
|
118
|
-
|
|
166
|
+
|
|
119
167
|
if not found:
|
|
120
168
|
print("[red]Timed out waiting for toolkit to join the room[/red]")
|
|
121
169
|
raise typer.Exit(1)
|
|
122
170
|
|
|
123
|
-
|
|
124
171
|
print("[bold green]Invoking tool...[/bold green]")
|
|
125
172
|
parsed_context = json.loads(caller_context) if caller_context else None
|
|
126
173
|
response = await client.agents.invoke_tool(
|
|
@@ -144,10 +191,10 @@ async def list_agents_command(
|
|
|
144
191
|
*,
|
|
145
192
|
project_id: str = None,
|
|
146
193
|
room: Annotated[str, typer.Option()],
|
|
147
|
-
token_path: Annotated[Optional[str], typer.Option()] = None,
|
|
194
|
+
token_path: Annotated[Optional[str], typer.Option()] = None,
|
|
148
195
|
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
149
196
|
name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
|
|
150
|
-
role: str = "user"
|
|
197
|
+
role: str = "user",
|
|
151
198
|
):
|
|
152
199
|
"""
|
|
153
200
|
List all agents available in the room.
|
|
@@ -156,28 +203,39 @@ async def list_agents_command(
|
|
|
156
203
|
try:
|
|
157
204
|
project_id = await resolve_project_id(project_id=project_id)
|
|
158
205
|
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
159
|
-
jwt = await resolve_token_jwt(
|
|
206
|
+
jwt = await resolve_token_jwt(
|
|
207
|
+
project_id=project_id,
|
|
208
|
+
api_key_id=api_key_id,
|
|
209
|
+
token_path=token_path,
|
|
210
|
+
name=name,
|
|
211
|
+
role=role,
|
|
212
|
+
room=room,
|
|
213
|
+
)
|
|
160
214
|
|
|
161
215
|
print("[bold green]Connecting to room...[/bold green]")
|
|
162
216
|
async with RoomClient(
|
|
163
|
-
protocol=WebSocketClientProtocol(
|
|
164
|
-
|
|
217
|
+
protocol=WebSocketClientProtocol(
|
|
218
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
219
|
+
token=jwt,
|
|
220
|
+
)
|
|
165
221
|
) as client:
|
|
166
222
|
print("[bold green]Fetching list of agents...[/bold green]")
|
|
167
223
|
agents = await client.agents.list_agents()
|
|
168
224
|
# Format the output as JSON
|
|
169
225
|
output = []
|
|
170
226
|
for agent in agents:
|
|
171
|
-
output.append(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
227
|
+
output.append(
|
|
228
|
+
{
|
|
229
|
+
"name": agent.name,
|
|
230
|
+
"title": agent.title,
|
|
231
|
+
"description": agent.description,
|
|
232
|
+
"requires": [r.to_json() for r in agent.requires],
|
|
233
|
+
"supports_tools": agent.supports_tools,
|
|
234
|
+
"labels": agent.labels,
|
|
235
|
+
}
|
|
236
|
+
)
|
|
179
237
|
print(json.dumps(output, indent=2))
|
|
180
|
-
|
|
238
|
+
|
|
181
239
|
finally:
|
|
182
240
|
await account_client.close()
|
|
183
241
|
|
|
@@ -187,11 +245,13 @@ async def list_toolkits_command(
|
|
|
187
245
|
*,
|
|
188
246
|
project_id: str = None,
|
|
189
247
|
room: Annotated[str, typer.Option()],
|
|
190
|
-
token_path: Annotated[Optional[str], typer.Option()] = None,
|
|
248
|
+
token_path: Annotated[Optional[str], typer.Option()] = None,
|
|
191
249
|
api_key_id: Annotated[Optional[str], typer.Option()] = None,
|
|
192
250
|
name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
|
|
193
251
|
role: str = "user",
|
|
194
|
-
participant_id: Annotated[
|
|
252
|
+
participant_id: Annotated[
|
|
253
|
+
Optional[str], typer.Option(..., help="Optional participant ID")
|
|
254
|
+
] = None,
|
|
195
255
|
):
|
|
196
256
|
"""
|
|
197
257
|
List all toolkits (and tools within them) available in the room.
|
|
@@ -200,40 +260,49 @@ async def list_toolkits_command(
|
|
|
200
260
|
try:
|
|
201
261
|
project_id = await resolve_project_id(project_id=project_id)
|
|
202
262
|
api_key_id = await resolve_api_key(project_id, api_key_id)
|
|
203
|
-
jwt = await resolve_token_jwt(
|
|
263
|
+
jwt = await resolve_token_jwt(
|
|
264
|
+
project_id=project_id,
|
|
265
|
+
api_key_id=api_key_id,
|
|
266
|
+
token_path=token_path,
|
|
267
|
+
name=name,
|
|
268
|
+
role=role,
|
|
269
|
+
room=room,
|
|
270
|
+
)
|
|
204
271
|
|
|
205
272
|
print("[bold green]Connecting to room...[/bold green]")
|
|
206
273
|
async with RoomClient(
|
|
207
|
-
protocol=WebSocketClientProtocol(
|
|
208
|
-
|
|
274
|
+
protocol=WebSocketClientProtocol(
|
|
275
|
+
url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
|
|
276
|
+
token=jwt,
|
|
277
|
+
)
|
|
209
278
|
) as client:
|
|
210
279
|
print("[bold green]Fetching list of toolkits...[/bold green]")
|
|
211
280
|
toolkits = await client.agents.list_toolkits(participant_id=participant_id)
|
|
212
|
-
|
|
281
|
+
|
|
213
282
|
# Format and output as JSON
|
|
214
283
|
output = []
|
|
215
284
|
for tk in toolkits:
|
|
216
|
-
output.append(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
285
|
+
output.append(
|
|
286
|
+
{
|
|
287
|
+
"name": tk.name,
|
|
288
|
+
"title": tk.title,
|
|
289
|
+
"description": tk.description,
|
|
290
|
+
"thumbnail_url": tk.thumbnail_url,
|
|
291
|
+
"tools": [
|
|
292
|
+
{
|
|
293
|
+
"name": tool.name,
|
|
294
|
+
"title": tool.title,
|
|
295
|
+
"description": tool.description,
|
|
296
|
+
"input_schema": tool.input_schema,
|
|
297
|
+
"thumbnail_url": tool.thumbnail_url,
|
|
298
|
+
"defs": tool.defs,
|
|
299
|
+
"supports_context": tool.supports_context,
|
|
300
|
+
}
|
|
301
|
+
for tool in tk.tools
|
|
302
|
+
],
|
|
303
|
+
}
|
|
304
|
+
)
|
|
234
305
|
print(json.dumps(output, indent=2))
|
|
235
|
-
|
|
306
|
+
|
|
236
307
|
finally:
|
|
237
308
|
await account_client.close()
|
|
238
|
-
|
|
239
|
-
|
meshagent/cli/api_keys.py
CHANGED
|
@@ -1,39 +1,57 @@
|
|
|
1
1
|
import typer
|
|
2
|
+
import json
|
|
2
3
|
from rich import print
|
|
4
|
+
from typing import Annotated
|
|
3
5
|
from meshagent.cli import async_typer
|
|
4
6
|
from meshagent.cli.helper import get_client, print_json_table
|
|
5
|
-
from meshagent.cli.helper import
|
|
7
|
+
from meshagent.cli.helper import resolve_project_id, set_active_api_key
|
|
6
8
|
|
|
7
9
|
app = async_typer.AsyncTyper()
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
@app.async_command("list")
|
|
11
|
-
async def list(
|
|
12
|
-
|
|
13
|
+
async def list(
|
|
14
|
+
*,
|
|
15
|
+
project_id: str = None,
|
|
16
|
+
o: Annotated[
|
|
17
|
+
str,
|
|
18
|
+
typer.Option(
|
|
19
|
+
"--output",
|
|
20
|
+
"-o",
|
|
21
|
+
help="output format [json|table]",
|
|
22
|
+
),
|
|
23
|
+
] = "table",
|
|
24
|
+
):
|
|
13
25
|
project_id = await resolve_project_id(project_id=project_id)
|
|
14
|
-
|
|
15
26
|
client = await get_client()
|
|
16
27
|
keys = (await client.list_project_api_keys(project_id=project_id))["keys"]
|
|
17
28
|
if len(keys) > 0:
|
|
18
|
-
|
|
29
|
+
if o == "json":
|
|
30
|
+
sanitized_keys = [
|
|
31
|
+
{k: v for k, v in key.items() if k != "created_by"} for key in keys
|
|
32
|
+
]
|
|
33
|
+
print(json.dumps({"api-keys": sanitized_keys}, indent=2))
|
|
34
|
+
else:
|
|
35
|
+
print_json_table(keys, "id", "name", "description")
|
|
19
36
|
else:
|
|
20
37
|
print("There are not currently any API keys in the project")
|
|
21
38
|
await client.close()
|
|
22
39
|
|
|
40
|
+
|
|
23
41
|
@app.async_command("create")
|
|
24
42
|
async def create(*, project_id: str = None, name: str, description: str = ""):
|
|
25
|
-
|
|
26
43
|
project_id = await resolve_project_id(project_id=project_id)
|
|
27
44
|
|
|
28
45
|
client = await get_client()
|
|
29
|
-
api_key = await client.create_project_api_key(
|
|
46
|
+
api_key = await client.create_project_api_key(
|
|
47
|
+
project_id=project_id, name=name, description=description
|
|
48
|
+
)
|
|
30
49
|
print(api_key["token"])
|
|
31
50
|
await client.close()
|
|
32
51
|
|
|
33
52
|
|
|
34
53
|
@app.async_command("delete")
|
|
35
54
|
async def delete(*, project_id: str = None, id: str):
|
|
36
|
-
|
|
37
55
|
project_id = await resolve_project_id(project_id=project_id)
|
|
38
56
|
|
|
39
57
|
client = await get_client()
|
|
@@ -50,7 +68,7 @@ async def show(*, project_id: str = None, api_key_id: str):
|
|
|
50
68
|
key = await client.decrypt_project_api_key(project_id=project_id, id=api_key_id)
|
|
51
69
|
|
|
52
70
|
print(key["token"])
|
|
53
|
-
|
|
71
|
+
|
|
54
72
|
finally:
|
|
55
73
|
await client.close()
|
|
56
74
|
|
meshagent/cli/auth.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
|
|
3
3
|
from meshagent.cli import async_typer
|
|
4
|
-
from meshagent.cli import auth_async
|
|
5
|
-
from meshagent.cli.helper import
|
|
4
|
+
from meshagent.cli import auth_async
|
|
5
|
+
from meshagent.cli.helper import get_active_project
|
|
6
6
|
|
|
7
7
|
app = async_typer.AsyncTyper()
|
|
8
8
|
|
|
@@ -12,13 +12,18 @@ async def login():
|
|
|
12
12
|
await auth_async.login()
|
|
13
13
|
|
|
14
14
|
project_id = await get_active_project()
|
|
15
|
-
if project_id
|
|
16
|
-
print(
|
|
15
|
+
if project_id is None:
|
|
16
|
+
print(
|
|
17
|
+
"You have been logged in, but you haven"
|
|
18
|
+
't activated a project yet, list your projects with "meshagent project list" and then activate one with "meshagent project activate PROJECT_ID"'
|
|
19
|
+
)
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
@app.async_command("logout")
|
|
19
23
|
async def logout():
|
|
20
24
|
await auth_async.logout()
|
|
21
25
|
|
|
26
|
+
|
|
22
27
|
@app.async_command("whoami")
|
|
23
28
|
async def whoami():
|
|
24
29
|
_, s = await auth_async.session()
|
meshagent/cli/auth_async.py
CHANGED
|
@@ -1,49 +1,67 @@
|
|
|
1
|
-
import os
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import webbrowser
|
|
4
|
+
import asyncio
|
|
2
5
|
from pathlib import Path
|
|
3
6
|
from aiohttp import web
|
|
4
|
-
from supabase._async.client import
|
|
7
|
+
from supabase._async.client import (
|
|
8
|
+
AsyncClient,
|
|
9
|
+
create_client,
|
|
10
|
+
) # async flavour :contentReference[oaicite:1]{index=1}
|
|
5
11
|
from supabase.lib.client_options import ClientOptions
|
|
6
12
|
from gotrue import AsyncMemoryStorage
|
|
7
13
|
|
|
8
|
-
AUTH_URL
|
|
9
|
-
AUTH_ANON_KEY
|
|
10
|
-
|
|
14
|
+
AUTH_URL = os.getenv("MESHAGENT_AUTH_URL", "https://infra.meshagent.com")
|
|
15
|
+
AUTH_ANON_KEY = os.getenv(
|
|
16
|
+
"MESHAGENT_AUTH_ANON_KEY",
|
|
17
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5memhyeWpoc3RjZXdkeWdvampzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQ2MzU2MjgsImV4cCI6MjA1MDIxMTYyOH0.ujx9CIbYEvWbA77ogB1gg1Jrv3KtpB1rWh_LRRLpcow",
|
|
18
|
+
)
|
|
19
|
+
CACHE_FILE = Path.home() / ".meshagent" / "session.json"
|
|
11
20
|
REDIRECT_PORT = 8765
|
|
12
|
-
REDIRECT_URL
|
|
21
|
+
REDIRECT_URL = f"http://localhost:{REDIRECT_PORT}/callback"
|
|
13
22
|
|
|
14
23
|
# ---------- helpers ----------------------------------------------------------
|
|
15
24
|
|
|
25
|
+
|
|
16
26
|
def _ensure_cache_dir():
|
|
17
27
|
CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
18
28
|
|
|
29
|
+
|
|
19
30
|
async def _client() -> AsyncClient:
|
|
20
31
|
return await create_client(
|
|
21
32
|
AUTH_URL,
|
|
22
33
|
AUTH_ANON_KEY,
|
|
23
34
|
options=ClientOptions(
|
|
24
|
-
flow_type="pkce",
|
|
35
|
+
flow_type="pkce", # OAuth + PKCE :contentReference[oaicite:2]{index=2}
|
|
25
36
|
auto_refresh_token=True,
|
|
26
37
|
persist_session=False,
|
|
27
|
-
storage=AsyncMemoryStorage()
|
|
38
|
+
storage=AsyncMemoryStorage(),
|
|
28
39
|
),
|
|
29
40
|
)
|
|
30
41
|
|
|
42
|
+
|
|
31
43
|
def _save(s):
|
|
32
44
|
_ensure_cache_dir()
|
|
33
|
-
CACHE_FILE.write_text(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
CACHE_FILE.write_text(
|
|
46
|
+
json.dumps(
|
|
47
|
+
{
|
|
48
|
+
"access_token": s.access_token,
|
|
49
|
+
"refresh_token": s.refresh_token,
|
|
50
|
+
"expires_at": s.expires_at, # int (seconds since epoch)
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
38
55
|
|
|
39
|
-
|
|
40
56
|
def _load():
|
|
41
57
|
_ensure_cache_dir()
|
|
42
58
|
if CACHE_FILE.exists():
|
|
43
59
|
return json.loads(CACHE_FILE.read_text())
|
|
44
60
|
|
|
61
|
+
|
|
45
62
|
# ---------- local HTTP callback ---------------------------------------------
|
|
46
63
|
|
|
64
|
+
|
|
47
65
|
async def _wait_for_code() -> str:
|
|
48
66
|
"""Spin up a one-shot aiohttp server and await ?code=…"""
|
|
49
67
|
app = web.Application()
|
|
@@ -68,8 +86,10 @@ async def _wait_for_code() -> str:
|
|
|
68
86
|
finally:
|
|
69
87
|
await runner.cleanup()
|
|
70
88
|
|
|
89
|
+
|
|
71
90
|
# ---------- public API -------------------------------------------------------
|
|
72
91
|
|
|
92
|
+
|
|
73
93
|
async def login():
|
|
74
94
|
supa = await _client()
|
|
75
95
|
|
|
@@ -79,7 +99,7 @@ async def login():
|
|
|
79
99
|
"provider": "google",
|
|
80
100
|
"options": {"redirect_to": REDIRECT_URL},
|
|
81
101
|
}
|
|
82
|
-
)
|
|
102
|
+
) # :contentReference[oaicite:3]{index=3}
|
|
83
103
|
oauth_url = res.url
|
|
84
104
|
|
|
85
105
|
# 2️⃣ Kick user to browser without blocking the loop
|
|
@@ -89,20 +109,22 @@ async def login():
|
|
|
89
109
|
# 3️⃣ Await the auth code, then exchange for tokens
|
|
90
110
|
auth_code = await _wait_for_code()
|
|
91
111
|
print("Got code, exchanging…")
|
|
92
|
-
sess = await supa.auth.exchange_code_for_session({"auth_code": auth_code}) #
|
|
112
|
+
sess = await supa.auth.exchange_code_for_session({"auth_code": auth_code}) #
|
|
93
113
|
_save(sess.session)
|
|
94
114
|
print("✅ Logged in as", sess.user.email)
|
|
95
115
|
|
|
116
|
+
|
|
96
117
|
async def session():
|
|
97
118
|
supa = await _client()
|
|
98
119
|
cached = _load()
|
|
99
120
|
fresh = None
|
|
100
121
|
if cached:
|
|
101
122
|
await supa.auth.set_session(cached["access_token"], cached["refresh_token"])
|
|
102
|
-
fresh = await supa.auth.get_session()
|
|
123
|
+
fresh = await supa.auth.get_session() # returns a Session object
|
|
103
124
|
_save(fresh)
|
|
104
125
|
return supa, fresh
|
|
105
126
|
|
|
127
|
+
|
|
106
128
|
async def logout():
|
|
107
129
|
supa, s = await session()
|
|
108
130
|
if s:
|
|
@@ -110,6 +132,7 @@ async def logout():
|
|
|
110
132
|
CACHE_FILE.unlink(missing_ok=True)
|
|
111
133
|
print("👋 Signed out")
|
|
112
134
|
|
|
135
|
+
|
|
113
136
|
async def get_access_token():
|
|
114
137
|
supa, fresh = await session()
|
|
115
138
|
return fresh.access_token
|