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/storage.py CHANGED
@@ -6,15 +6,21 @@ import fnmatch
6
6
  import glob
7
7
  import shutil
8
8
 
9
- from meshagent.api import RoomClient, ParticipantToken, WebSocketClientProtocol
9
+ from meshagent.api import RoomClient, WebSocketClientProtocol
10
10
  from meshagent.api.room_server_client import StorageClient
11
11
  from meshagent.api.helpers import meshagent_base_url, websocket_room_url
12
12
  from meshagent.cli import async_typer
13
- from meshagent.cli.helper import get_client, resolve_project_id, resolve_api_key, resolve_token_jwt
14
- from meshagent.cli.helper import get_client, resolve_project_id, resolve_api_key, resolve_token_jwt, resolve_room
13
+ from meshagent.cli.helper import (
14
+ get_client,
15
+ resolve_project_id,
16
+ resolve_api_key,
17
+ resolve_token_jwt,
18
+ )
19
+ from meshagent.cli.helper import resolve_room
15
20
 
16
21
  app = async_typer.AsyncTyper()
17
22
 
23
+
18
24
  def parse_path(path: str):
19
25
  """
20
26
  Parse a path and return a tuple (scheme, subpath).
@@ -23,9 +29,10 @@ def parse_path(path: str):
23
29
  """
24
30
  prefix = "room://"
25
31
  if path.startswith(prefix):
26
- return ("room", path[len(prefix):])
32
+ return ("room", path[len(prefix) :])
27
33
  return ("local", path)
28
34
 
35
+
29
36
  def split_glob_subpath(subpath: str):
30
37
  """
31
38
  Given something like "folder/*.txt" or "folder/subfolder/*.json",
@@ -51,11 +58,11 @@ async def storage_exists_command(
51
58
  *,
52
59
  project_id: str = None,
53
60
  room: Annotated[str, typer.Option(..., help="Room name")],
54
- token_path: Annotated[Optional[str], typer.Option()] = None,
61
+ token_path: Annotated[Optional[str], typer.Option()] = None,
55
62
  api_key_id: Annotated[Optional[str], typer.Option(..., help="API Key ID")] = None,
56
63
  name: Annotated[str, typer.Option(..., help="Participant name")] = "cli",
57
64
  role: str = "user",
58
- path: str
65
+ path: str,
59
66
  ):
60
67
  """
61
68
  Check if a file/folder exists in remote storage.
@@ -65,21 +72,29 @@ async def storage_exists_command(
65
72
  project_id = await resolve_project_id(project_id=project_id)
66
73
  api_key_id = await resolve_api_key(project_id, api_key_id)
67
74
  room = resolve_room(room)
68
- 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)
69
-
75
+ jwt = await resolve_token_jwt(
76
+ project_id=project_id,
77
+ api_key_id=api_key_id,
78
+ token_path=token_path,
79
+ name=name,
80
+ role=role,
81
+ room=room,
82
+ )
83
+
70
84
  print("[bold green]Connecting to room...[/bold green]")
71
85
  async with RoomClient(
72
86
  protocol=WebSocketClientProtocol(
73
87
  url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
74
- token=jwt
88
+ token=jwt,
75
89
  )
76
90
  ) as client:
77
-
78
91
  file_exists = await client.storage.exists(path=path)
79
92
  if file_exists:
80
93
  print(f"[bold cyan]'{path}' exists in remote storage.[/bold cyan]")
81
94
  else:
82
- print(f"[bold red]'{path}' does NOT exist in remote storage.[/bold red]")
95
+ print(
96
+ f"[bold red]'{path}' does NOT exist in remote storage.[/bold red]"
97
+ )
83
98
  finally:
84
99
  await account_client.close()
85
100
 
@@ -88,16 +103,22 @@ async def storage_exists_command(
88
103
  async def storage_cp_command(
89
104
  *,
90
105
  project_id: str = None,
91
- room: Annotated[str, typer.Option(..., help="Room name (if copying to/from remote)")],
92
- token_path: Annotated[Optional[str], typer.Option()] = None,
93
- api_key_id: Annotated[str, typer.Option(..., help="API Key ID (if copying to/from remote)")],
94
- name: Annotated[str, typer.Option(..., help="Participant name (if copying to/from remote)")],
106
+ room: Annotated[
107
+ str, typer.Option(..., help="Room name (if copying to/from remote)")
108
+ ],
109
+ token_path: Annotated[Optional[str], typer.Option()] = None,
110
+ api_key_id: Annotated[
111
+ str, typer.Option(..., help="API Key ID (if copying to/from remote)")
112
+ ] = None,
113
+ name: Annotated[
114
+ str, typer.Option(..., help="Participant name (if copying to/from remote)")
115
+ ],
95
116
  role: str = "user",
96
117
  source_path: str,
97
- dest_path: str
118
+ dest_path: str,
98
119
  ):
99
120
  room = resolve_room(room)
100
-
121
+
101
122
  try:
102
123
  """
103
124
  Copy files between local and remote storage. Supports globs on the source side.
@@ -114,7 +135,7 @@ async def storage_cp_command(
114
135
  # (or both are remote).
115
136
  account_client = None
116
137
  client = None
117
- storage_client : None | StorageClient = None
138
+ storage_client: None | StorageClient = None
118
139
 
119
140
  # A helper to ensure we have a connected StorageClient if needed
120
141
  async def ensure_storage_client():
@@ -126,14 +147,23 @@ async def storage_cp_command(
126
147
  account_client = await get_client()
127
148
  project_id = await resolve_project_id(project_id=project_id)
128
149
  api_key_id = await resolve_api_key(project_id, api_key_id)
129
-
130
- 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)
131
-
150
+
151
+ jwt = await resolve_token_jwt(
152
+ project_id=project_id,
153
+ api_key_id=api_key_id,
154
+ token_path=token_path,
155
+ name=name,
156
+ role=role,
157
+ room=room,
158
+ )
159
+
132
160
  print("[bold green]Connecting to room...[/bold green]")
133
161
  client = RoomClient(
134
162
  protocol=WebSocketClientProtocol(
135
- url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
136
- token=jwt
163
+ url=websocket_room_url(
164
+ room_name=room, base_url=meshagent_base_url()
165
+ ),
166
+ token=jwt,
137
167
  )
138
168
  )
139
169
 
@@ -154,7 +184,9 @@ async def storage_cp_command(
154
184
  # We'll just see if it exists
155
185
  file_exists = await sc.exists(path=base_dir)
156
186
  if not file_exists:
157
- raise FileNotFoundError(f"Remote file '{path_pattern}' does not exist.")
187
+ raise FileNotFoundError(
188
+ f"Remote file '{path_pattern}' does not exist."
189
+ )
158
190
  return [(base_dir, os.path.basename(base_dir))] # (full, filename)
159
191
  else:
160
192
  # List base_dir, filter
@@ -176,7 +208,9 @@ async def storage_cp_command(
176
208
  print(f"[bold red]No local files match '{src_subpath}'[/bold red]")
177
209
  return
178
210
  # We'll store (absolute_source_file, filename_only)
179
- expanded_sources = [(m, os.path.basename(m)) for m in local_matches if os.path.isfile(m)]
211
+ expanded_sources = [
212
+ (m, os.path.basename(m)) for m in local_matches if os.path.isfile(m)
213
+ ]
180
214
  else:
181
215
  # Single local file
182
216
  if not os.path.isfile(src_subpath):
@@ -194,12 +228,16 @@ async def storage_cp_command(
194
228
 
195
229
  # 2) Figure out if destination is a single file or a directory
196
230
  # We'll handle multi-file -> directory or single-file -> single-file.
197
- multiple_sources = (len(expanded_sources) > 1)
231
+ multiple_sources = len(expanded_sources) > 1
198
232
 
199
233
  if dst_scheme == "local":
200
234
  # If local destination is a directory or ends with a path separator,
201
235
  # we treat it as a directory. If multiple files, it must be a directory.
202
- if os.path.isdir(dst_subpath) or dst_subpath.endswith(os.sep) or dst_subpath == "":
236
+ if (
237
+ os.path.isdir(dst_subpath)
238
+ or dst_subpath.endswith(os.sep)
239
+ or dst_subpath == ""
240
+ ):
203
241
  # directory
204
242
  for full_src, fname in expanded_sources:
205
243
  copy_operations.append((full_src, os.path.join(dst_subpath, fname)))
@@ -207,7 +245,9 @@ async def storage_cp_command(
207
245
  # single file (or maybe it doesn't exist yet, but no slash)
208
246
  if multiple_sources:
209
247
  # Must be a directory, but user gave a file-like name => error
210
- print(f"[bold red]Destination '{dest_path}' is not a directory, but multiple files are being copied.[/bold red]")
248
+ print(
249
+ f"[bold red]Destination '{dest_path}' is not a directory, but multiple files are being copied.[/bold red]"
250
+ )
211
251
  return
212
252
  # single file
213
253
  copy_operations.append((expanded_sources[0][0], dst_subpath))
@@ -223,8 +263,8 @@ async def storage_cp_command(
223
263
  # doesn't differentiate a folder from an empty folder. We'll keep it simple.
224
264
  is_destination_folder = False
225
265
  try:
226
- entries = await storage_client.list(path=dst_subpath)
227
- # If listing worked, it might be a folder (unless it's a file with children?).
266
+ await storage_client.list(path=dst_subpath)
267
+ # If listing worked, it might be a folder (unless it's a file with children?).
228
268
  # We'll assume it’s a folder if we get any results or no error.
229
269
  is_destination_folder = True
230
270
  except Exception:
@@ -240,7 +280,9 @@ async def storage_cp_command(
240
280
  else:
241
281
  # single file path
242
282
  if multiple_sources:
243
- print(f"[bold red]Destination '{dest_path}' is not a folder, but multiple files are being copied.[/bold red]")
283
+ print(
284
+ f"[bold red]Destination '{dest_path}' is not a folder, but multiple files are being copied.[/bold red]"
285
+ )
244
286
  return
245
287
  copy_operations.append((expanded_sources[0][0], dst_subpath))
246
288
 
@@ -251,7 +293,7 @@ async def storage_cp_command(
251
293
  # b) local->remote
252
294
  # c) remote->local
253
295
  # d) remote->remote
254
- for (src_file, dst_file) in copy_operations:
296
+ for src_file, dst_file in copy_operations:
255
297
  # Determine combo
256
298
  if src_scheme == "local" and dst_scheme == "local":
257
299
  # local->local
@@ -259,7 +301,9 @@ async def storage_cp_command(
259
301
  os.makedirs(os.path.dirname(dst_file), exist_ok=True)
260
302
  with open(src_file, "rb") as fsrc, open(dst_file, "wb") as fdst:
261
303
  fdst.write(fsrc.read())
262
- print(f"[bold cyan]Copied local '{src_file}' to '{dst_file}'[/bold cyan]")
304
+ print(
305
+ f"[bold cyan]Copied local '{src_file}' to '{dst_file}'[/bold cyan]"
306
+ )
263
307
 
264
308
  elif src_scheme == "local" and dst_scheme == "room":
265
309
  # local->remote
@@ -270,7 +314,9 @@ async def storage_cp_command(
270
314
  dest_handle = await storage_client.open(path=dst_file, overwrite=True)
271
315
  await storage_client.write(handle=dest_handle, data=data)
272
316
  await storage_client.close(handle=dest_handle)
273
- print(f"[bold cyan]Uploaded '{src_file}' to remote '{dst_file}'[/bold cyan]")
317
+ print(
318
+ f"[bold cyan]Uploaded '{src_file}' to remote '{dst_file}'[/bold cyan]"
319
+ )
274
320
 
275
321
  elif src_scheme == "room" and dst_scheme == "local":
276
322
  # remote->local
@@ -280,7 +326,9 @@ async def storage_cp_command(
280
326
  os.makedirs(os.path.dirname(dst_file), exist_ok=True)
281
327
  with open(dst_file, "wb") as fdst:
282
328
  fdst.write(remote_file.data)
283
- print(f"[bold cyan]Downloaded remote '{src_file}' to local '{dst_file}'[/bold cyan]")
329
+ print(
330
+ f"[bold cyan]Downloaded remote '{src_file}' to local '{dst_file}'[/bold cyan]"
331
+ )
284
332
 
285
333
  else:
286
334
  # remote->remote
@@ -289,7 +337,9 @@ async def storage_cp_command(
289
337
  dest_handle = await storage_client.open(path=dst_file, overwrite=True)
290
338
  await storage_client.write(handle=dest_handle, data=source_file.data)
291
339
  await storage_client.close(handle=dest_handle)
292
- print(f"[bold cyan]Copied remote '{src_file}' to '{dst_file}'[/bold cyan]")
340
+ print(
341
+ f"[bold cyan]Copied remote '{src_file}' to '{dst_file}'[/bold cyan]"
342
+ )
293
343
  finally:
294
344
  # Clean up
295
345
  if client is not None:
@@ -301,22 +351,32 @@ async def storage_cp_command(
301
351
  @app.async_command("show")
302
352
  async def storage_show_command(
303
353
  *,
304
- project_id: Annotated[Optional[str], typer.Option(..., help="Project ID (if remote)")] = None,
305
- room: Annotated[Optional[str], typer.Option(..., help="Room name (if remote)")] = None,
306
- token_path: Annotated[Optional[str], typer.Option()] = None,
307
- api_key_id: Annotated[Optional[str], typer.Option(..., help="API Key ID (if remote)")] = None,
308
- name: Annotated[Optional[str], typer.Option(..., help="Participant name (if remote)")] = None,
354
+ project_id: Annotated[
355
+ Optional[str], typer.Option(..., help="Project ID (if remote)")
356
+ ] = None,
357
+ room: Annotated[
358
+ Optional[str], typer.Option(..., help="Room name (if remote)")
359
+ ] = None,
360
+ token_path: Annotated[Optional[str], typer.Option()] = None,
361
+ api_key_id: Annotated[
362
+ Optional[str], typer.Option(..., help="API Key ID (if remote)")
363
+ ] = None,
364
+ name: Annotated[
365
+ Optional[str], typer.Option(..., help="Participant name (if remote)")
366
+ ] = None,
309
367
  role: str = "user",
310
368
  path: str,
311
- encoding: Annotated[str, typer.Option("--encoding", help="Text encoding")] = "utf-8"
369
+ encoding: Annotated[
370
+ str, typer.Option("--encoding", help="Text encoding")
371
+ ] = "utf-8",
312
372
  ):
313
373
  """
314
374
  Print the contents of a file (local or remote) to the console.
315
375
  """
316
376
  room = resolve_room(room)
317
-
377
+
318
378
  scheme, subpath = parse_path(path)
319
-
379
+
320
380
  # If we need a remote connection, set it up:
321
381
  account_client = None
322
382
  client = None
@@ -329,21 +389,26 @@ async def storage_show_command(
329
389
  return
330
390
 
331
391
  if not room:
332
- raise typer.BadParameter(
333
- "To show a remote file, you must provide --room"
334
- )
392
+ raise typer.BadParameter("To show a remote file, you must provide --room")
335
393
 
336
394
  account_client = await get_client()
337
395
  project_id = await resolve_project_id(project_id=project_id)
338
396
  api_key_id = await resolve_api_key(project_id, api_key_id)
339
397
 
340
- 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)
341
-
398
+ jwt = await resolve_token_jwt(
399
+ project_id=project_id,
400
+ api_key_id=api_key_id,
401
+ token_path=token_path,
402
+ name=name,
403
+ role=role,
404
+ room=room,
405
+ )
406
+
342
407
  print("[bold green]Connecting to room...[/bold green]")
343
408
  client = RoomClient(
344
409
  protocol=WebSocketClientProtocol(
345
410
  url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
346
- token=jwt
411
+ token=jwt,
347
412
  )
348
413
  )
349
414
 
@@ -379,14 +444,24 @@ async def storage_show_command(
379
444
  @app.async_command("rm")
380
445
  async def storage_rm_command(
381
446
  *,
382
- project_id: Annotated[Optional[str], typer.Option(..., help="Project ID (if remote)")] = None,
383
- room: Annotated[Optional[str], typer.Option(..., help="Room name (if remote)")] = None,
384
- token_path: Annotated[Optional[str], typer.Option()] = None,
385
- api_key_id: Annotated[Optional[str], typer.Option(..., help="API Key ID (if remote)")] = None,
386
- name: Annotated[Optional[str], typer.Option(..., help="Participant name (if remote)")] = None,
447
+ project_id: Annotated[
448
+ Optional[str], typer.Option(..., help="Project ID (if remote)")
449
+ ] = None,
450
+ room: Annotated[
451
+ Optional[str], typer.Option(..., help="Room name (if remote)")
452
+ ] = None,
453
+ token_path: Annotated[Optional[str], typer.Option()] = None,
454
+ api_key_id: Annotated[
455
+ Optional[str], typer.Option(..., help="API Key ID (if remote)")
456
+ ] = None,
457
+ name: Annotated[
458
+ Optional[str], typer.Option(..., help="Participant name (if remote)")
459
+ ] = None,
387
460
  role: str = "user",
388
461
  path: str,
389
- recursive: Annotated[bool, typer.Option("-r", help="Remove directories/folders recursively")] = False,
462
+ recursive: Annotated[
463
+ bool, typer.Option("-r", help="Remove directories/folders recursively")
464
+ ] = False,
390
465
  ):
391
466
  """
392
467
  Remove files (and optionally folders) either locally or in remote storage.
@@ -406,7 +481,7 @@ async def storage_rm_command(
406
481
 
407
482
  # Helper to ensure we have a storage client if we need remote operations
408
483
  async def ensure_storage_client():
409
- nonlocal account_client, client, storage_client, api_key_id
484
+ nonlocal account_client, client, storage_client, project_id, api_key_id
410
485
 
411
486
  if storage_client is not None:
412
487
  return # Already set up
@@ -418,13 +493,23 @@ async def storage_rm_command(
418
493
 
419
494
  account_client = await get_client()
420
495
  project_id = await resolve_project_id(project_id=project_id)
421
- 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)
496
+ resolved_project_id = await resolve_project_id(project_id=project_id)
497
+ jwt = await resolve_token_jwt(
498
+ project_id=resolved_project_id,
499
+ api_key_id=api_key_id,
500
+ token_path=token_path,
501
+ name=name,
502
+ role=role,
503
+ room=room,
504
+ )
422
505
 
423
506
  print("[bold green]Connecting to room...[/bold green]")
424
507
  client = RoomClient(
425
508
  protocol=WebSocketClientProtocol(
426
- url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
427
- token=jwt
509
+ url=websocket_room_url(
510
+ room_name=room, base_url=meshagent_base_url()
511
+ ),
512
+ token=jwt,
428
513
  )
429
514
  )
430
515
 
@@ -440,7 +525,9 @@ async def storage_rm_command(
440
525
  def remove_local_path(local_path: str, recursive: bool):
441
526
  """Remove a single local path (file or directory). Respects 'recursive' for directories."""
442
527
  if not os.path.exists(local_path):
443
- print(f"[yellow]Local path '{local_path}' does not exist. Skipping.[/yellow]")
528
+ print(
529
+ f"[yellow]Local path '{local_path}' does not exist. Skipping.[/yellow]"
530
+ )
444
531
  return
445
532
 
446
533
  if os.path.isfile(local_path):
@@ -448,13 +535,19 @@ async def storage_rm_command(
448
535
  print(f"[bold cyan]Removed local file '{local_path}'[/bold cyan]")
449
536
  elif os.path.isdir(local_path):
450
537
  if not recursive:
451
- print(f"[bold red]Cannot remove directory '{local_path}' without -r.[/bold red]")
538
+ print(
539
+ f"[bold red]Cannot remove directory '{local_path}' without -r.[/bold red]"
540
+ )
452
541
  raise typer.Exit(code=1)
453
542
  shutil.rmtree(local_path)
454
- print(f"[bold cyan]Removed local directory '{local_path}' recursively[/bold cyan]")
543
+ print(
544
+ f"[bold cyan]Removed local directory '{local_path}' recursively[/bold cyan]"
545
+ )
455
546
  else:
456
547
  # Neither file nor directory?
457
- print(f"[bold red]'{local_path}' is not a regular file or directory. Skipping.[/bold red]")
548
+ print(
549
+ f"[bold red]'{local_path}' is not a regular file or directory. Skipping.[/bold red]"
550
+ )
458
551
 
459
552
  # ---------------
460
553
  # REMOTE HELPERS
@@ -486,7 +579,7 @@ async def storage_rm_command(
486
579
  async def is_remote_folder(sc: StorageClient, remote_path: str) -> bool:
487
580
  """Return True if remote_path is a folder, otherwise False or it doesn't exist."""
488
581
  stat = await sc.stat(path=remote_path)
489
- if stat == None:
582
+ if stat is None:
490
583
  return False
491
584
  else:
492
585
  return stat.is_folder
@@ -498,14 +591,18 @@ async def storage_rm_command(
498
591
  """
499
592
  # Does it exist at all?
500
593
  if not await sc.exists(path=path):
501
- print(f"[yellow]Remote path '{path}' does not exist. Skipping.[/yellow]")
594
+ print(
595
+ f"[yellow]Remote path '{path}' does not exist. Skipping.[/yellow]"
596
+ )
502
597
  return
503
598
 
504
599
  # Check if it's a folder
505
600
  if await is_remote_folder(sc, path):
506
601
  # It's a folder
507
602
  if not recursive:
508
- print(f"[bold red]Cannot remove remote directory '{path}' without -r.[/bold red]")
603
+ print(
604
+ f"[bold red]Cannot remove remote directory '{path}' without -r.[/bold red]"
605
+ )
509
606
  raise typer.Exit(code=1)
510
607
 
511
608
  # Recursively remove contents
@@ -516,7 +613,9 @@ async def storage_rm_command(
516
613
 
517
614
  # Finally remove the folder itself (assuming storage.delete can remove empty folders)
518
615
  await sc.delete(path)
519
- print(f"[bold cyan]Removed remote directory '{path}' recursively[/bold cyan]")
616
+ print(
617
+ f"[bold cyan]Removed remote directory '{path}' recursively[/bold cyan]"
618
+ )
520
619
  else:
521
620
  # It's a file
522
621
  await sc.delete(path)
@@ -550,7 +649,7 @@ async def storage_rm_command(
550
649
  # ----------------------------------------------------------------
551
650
  # 2) Perform the removal
552
651
  # ----------------------------------------------------------------
553
- for (full_path, _) in expanded_targets:
652
+ for full_path, _ in expanded_targets:
554
653
  if scheme == "local":
555
654
  remove_local_path(full_path, recursive)
556
655
  else:
@@ -562,17 +661,30 @@ async def storage_rm_command(
562
661
  if account_client is not None:
563
662
  await account_client.close()
564
663
 
664
+
565
665
  @app.async_command("ls")
566
666
  async def storage_ls_command(
567
667
  *,
568
- project_id: Annotated[Optional[str], typer.Option(..., help="Project ID (if remote)")] = None,
569
- room: Annotated[Optional[str], typer.Option(..., help="Room name (if remote)")] = None,
570
- token_path: Annotated[Optional[str], typer.Option()] = None,
571
- api_key_id: Annotated[Optional[str], typer.Option(..., help="API Key ID (if remote)")] = None,
572
- name: Annotated[Optional[str], typer.Option(..., help="Participant name (if remote)")] = None,
668
+ project_id: Annotated[
669
+ Optional[str], typer.Option(..., help="Project ID (if remote)")
670
+ ] = None,
671
+ room: Annotated[
672
+ Optional[str], typer.Option(..., help="Room name (if remote)")
673
+ ] = None,
674
+ token_path: Annotated[Optional[str], typer.Option()] = None,
675
+ api_key_id: Annotated[
676
+ Optional[str], typer.Option(..., help="API Key ID (if remote)")
677
+ ] = None,
678
+ name: Annotated[
679
+ Optional[str], typer.Option(..., help="Participant name (if remote)")
680
+ ] = None,
573
681
  role: str = "user",
574
- path: Annotated[str, typer.Argument(..., help="Path to list (local or room://...)")],
575
- recursive: Annotated[bool, typer.Option("-r", help="List subfolders/files recursively")] = False,
682
+ path: Annotated[
683
+ str, typer.Argument(..., help="Path to list (local or room://...)")
684
+ ],
685
+ recursive: Annotated[
686
+ bool, typer.Option("-r", help="List subfolders/files recursively")
687
+ ] = False,
576
688
  ):
577
689
  """
578
690
  List files/folders either locally or in remote storage.
@@ -588,24 +700,29 @@ async def storage_ls_command(
588
700
 
589
701
  # --- Set up remote connection if needed ---
590
702
  async def ensure_storage_client():
591
- nonlocal account_client, client, storage_client, project_id
703
+ nonlocal account_client, client, storage_client, project_id, api_key_id
592
704
  if storage_client is not None:
593
705
  return
594
706
 
595
707
  if not room:
596
- raise typer.BadParameter(
597
- "To list a remote path, you must provide --room"
598
- )
708
+ raise typer.BadParameter("To list a remote path, you must provide --room")
599
709
 
600
710
  account_client = await get_client()
601
711
  project_id = await resolve_project_id(project_id=project_id)
602
-
603
- 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)
604
-
712
+ api_key_id = await resolve_api_key(project_id, api_key_id)
713
+ jwt = await resolve_token_jwt(
714
+ project_id=project_id,
715
+ api_key_id=api_key_id,
716
+ token_path=token_path,
717
+ name=name,
718
+ role=role,
719
+ room=room,
720
+ )
721
+
605
722
  client = RoomClient(
606
723
  protocol=WebSocketClientProtocol(
607
724
  url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
608
- token=jwt
725
+ token=jwt,
609
726
  )
610
727
  )
611
728
  await client.__aenter__()
@@ -619,7 +736,7 @@ async def storage_ls_command(
619
736
  List a local path. If it's a file, print it.
620
737
  If it's a directory, list its contents.
621
738
  If recursive=True, walk subdirectories as well.
622
-
739
+
623
740
  prefix is used to indent or prefix lines if desired.
624
741
  """
625
742
 
@@ -642,7 +759,11 @@ async def storage_ls_command(
642
759
  print(f"{prefix} {entry.name}/")
643
760
  if recursive:
644
761
  # Recursively list the folder
645
- list_local_path(os.path.join(base_path, entry.name), True, prefix + " ")
762
+ list_local_path(
763
+ os.path.join(base_path, entry.name),
764
+ True,
765
+ prefix + " ",
766
+ )
646
767
  else:
647
768
  print(f"{prefix} {entry.name}")
648
769
  except PermissionError:
@@ -668,18 +789,20 @@ async def storage_ls_command(
668
789
  async def is_remote_folder(sc: StorageClient, remote_path: str) -> bool:
669
790
  """Return True if remote_path is a folder, otherwise False or it doesn't exist."""
670
791
  stat = await sc.stat(path=remote_path)
671
-
672
- if stat == None:
792
+
793
+ if stat is None:
673
794
  return False
674
795
  else:
675
796
  return stat.is_folder
676
797
 
677
- async def list_remote_path(sc: StorageClient, remote_path: str, recursive: bool, prefix: str = ""):
798
+ async def list_remote_path(
799
+ sc: StorageClient, remote_path: str, recursive: bool, prefix: str = ""
800
+ ):
678
801
  """
679
802
  List a remote path. If it's a file, just print it.
680
803
  If it's a folder, list its contents. If recursive=True, list subfolders too.
681
804
  """
682
-
805
+
683
806
  # Does it exist at all?
684
807
  if not await sc.exists(path=remote_path):
685
808
  print(f"{prefix}[red]{remote_path} does not exist (remote)[/red]")
@@ -699,16 +822,22 @@ async def storage_ls_command(
699
822
  if e.is_folder:
700
823
  print(f"{prefix} {e.name}/")
701
824
  if recursive:
702
- await list_remote_path(sc, child_path, recursive, prefix + " ")
825
+ await list_remote_path(
826
+ sc, child_path, recursive, prefix + " "
827
+ )
703
828
  else:
704
829
  print(f"{prefix} {e.name}")
705
830
  except Exception as ex:
706
- print(f"{prefix}[red]Cannot list remote folder '{remote_path}': {ex}[/red]")
831
+ print(
832
+ f"{prefix}[red]Cannot list remote folder '{remote_path}': {ex}[/red]"
833
+ )
707
834
  else:
708
835
  # It's a file
709
836
  print(f"{prefix}{os.path.basename(remote_path)}")
710
837
 
711
- async def glob_and_list_remote(sc: StorageClient, path_pattern: str, recursive: bool):
838
+ async def glob_and_list_remote(
839
+ sc: StorageClient, path_pattern: str, recursive: bool
840
+ ):
712
841
  """
713
842
  If there's a wildcard, list matching files/folders. If no wildcard, list the single path.
714
843
  """
@@ -766,4 +895,3 @@ async def storage_ls_command(
766
895
  await client.__aexit__(None, None, None)
767
896
  if account_client is not None:
768
897
  await account_client.close()
769
-