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 CHANGED
@@ -1 +1,3 @@
1
- from .version import __version__
1
+ from .version import __version__
2
+
3
+ __all__ = [__version__]
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.services import send_webhook
9
- from meshagent.api import RoomClient, ParticipantToken, WebSocketClientProtocol, RoomException
10
- from meshagent.cli.helper import set_active_project, get_active_project, resolve_project_id, resolve_api_key
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, print_json_table, set_active_project, resolve_project_id, resolve_token_jwt
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(*, 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):
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 = (await account_client.decrypt_project_api_key(project_id=project_id, id=api_key_id))["token"]
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(protocol=WebSocketClientProtocol(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()), token=token.to_jwt(token=key))) as client:
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[str, typer.Option(..., help="JSON string with arguments for the tool")],
81
- participant_id: Annotated[Optional[str], typer.Option(..., help="Optional participant ID to invoke the tool on")] = None,
82
- on_behalf_of_id: Annotated[Optional[str], typer.Option(..., help="Optional 'on_behalf_of' participant ID")] = None,
83
- caller_context: Annotated[Optional[str], typer.Option(..., help="Optional JSON for caller context")] = None,
84
- timeout: Annotated[Optional[int], typer.Option(..., help="How long to wait for the toolkit if the toolkit is not in the room")] = 30,
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(project_id=project_id, api_key_id=api_key_id, token_path=token_path, name=name, role=role, room=room)
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(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
100
- token=jwt)
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(participant_id=participant_id)
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(project_id=project_id, api_key_id=api_key_id, token_path=token_path, name=name, role=role, room=room)
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(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
164
- token=jwt)
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
- "name": agent.name,
173
- "title": agent.title,
174
- "description": agent.description,
175
- "requires": [r.to_json() for r in agent.requires],
176
- "supports_tools": agent.supports_tools,
177
- "labels": agent.labels,
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[Optional[str], typer.Option(..., help="Optional participant ID")] = None
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(project_id=project_id, api_key_id=api_key_id, token_path=token_path, name=name, role=role, room=room)
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(url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
208
- token=jwt)
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
- "name": tk.name,
218
- "title": tk.title,
219
- "description": tk.description,
220
- "thumbnail_url": tk.thumbnail_url,
221
- "tools": [
222
- {
223
- "name": tool.name,
224
- "title": tool.title,
225
- "description": tool.description,
226
- "input_schema": tool.input_schema,
227
- "thumbnail_url": tool.thumbnail_url,
228
- "defs": tool.defs,
229
- "supports_context": tool.supports_context
230
- }
231
- for tool in tk.tools
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 set_active_project, get_active_project, resolve_project_id, set_active_api_key, resolve_api_key
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(*, project_id: str = None):
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
- print_json_table(keys, "id", "name", "description")
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(project_id=project_id, name=name, description=description)
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 set_active_project, get_active_project
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 == None:
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"')
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()
@@ -1,49 +1,67 @@
1
- import os, json, webbrowser, asyncio
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 AsyncClient, create_client # async flavour :contentReference[oaicite:1]{index=1}
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 = os.getenv("MESHAGENT_AUTH_URL", "https://infra.meshagent.com")
9
- AUTH_ANON_KEY = os.getenv("MESHAGENT_AUTH_ANON_KEY", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5memhyeWpoc3RjZXdkeWdvampzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQ2MzU2MjgsImV4cCI6MjA1MDIxMTYyOH0.ujx9CIbYEvWbA77ogB1gg1Jrv3KtpB1rWh_LRRLpcow")
10
- CACHE_FILE = Path.home() / ".meshagent" / "session.json"
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 = f"http://localhost:{REDIRECT_PORT}/callback"
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", # OAuth + PKCE :contentReference[oaicite:2]{index=2}
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(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
- }))
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
- ) # :contentReference[oaicite:3]{index=3}
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() # returns a Session object
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