meshagent-cli 0.0.37__py3-none-any.whl → 0.0.39__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.39.dist-info/METADATA +35 -0
- meshagent_cli-0.0.39.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.39.dist-info}/WHEEL +0 -0
- {meshagent_cli-0.0.37.dist-info → meshagent_cli-0.0.39.dist-info}/entry_points.txt +0 -0
- {meshagent_cli-0.0.37.dist-info → meshagent_cli-0.0.39.dist-info}/top_level.txt +0 -0
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,
|
|
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
|
|
14
|
-
|
|
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(
|
|
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(
|
|
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[
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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(
|
|
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(
|
|
136
|
-
|
|
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(
|
|
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 = [
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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[
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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[
|
|
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(
|
|
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[
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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[
|
|
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
|
-
|
|
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(
|
|
427
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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[
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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[
|
|
575
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|