meshagent-cli 0.7.0__py3-none-any.whl → 0.21.0__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.
Files changed (41) hide show
  1. meshagent/cli/agent.py +15 -11
  2. meshagent/cli/api_keys.py +4 -4
  3. meshagent/cli/async_typer.py +52 -4
  4. meshagent/cli/call.py +12 -8
  5. meshagent/cli/chatbot.py +1007 -129
  6. meshagent/cli/cli.py +21 -20
  7. meshagent/cli/cli_mcp.py +92 -28
  8. meshagent/cli/cli_secrets.py +10 -10
  9. meshagent/cli/common_options.py +19 -4
  10. meshagent/cli/containers.py +164 -16
  11. meshagent/cli/database.py +997 -0
  12. meshagent/cli/developer.py +3 -3
  13. meshagent/cli/exec.py +22 -6
  14. meshagent/cli/helper.py +62 -11
  15. meshagent/cli/helpers.py +66 -9
  16. meshagent/cli/host.py +37 -0
  17. meshagent/cli/mailbot.py +1004 -40
  18. meshagent/cli/mailboxes.py +223 -0
  19. meshagent/cli/meeting_transcriber.py +10 -4
  20. meshagent/cli/messaging.py +7 -7
  21. meshagent/cli/multi.py +402 -0
  22. meshagent/cli/oauth2.py +44 -21
  23. meshagent/cli/participant_token.py +5 -3
  24. meshagent/cli/port.py +70 -0
  25. meshagent/cli/queue.py +2 -2
  26. meshagent/cli/room.py +20 -212
  27. meshagent/cli/rooms.py +214 -0
  28. meshagent/cli/services.py +32 -23
  29. meshagent/cli/sessions.py +5 -5
  30. meshagent/cli/storage.py +5 -5
  31. meshagent/cli/task_runner.py +770 -0
  32. meshagent/cli/version.py +1 -1
  33. meshagent/cli/voicebot.py +502 -76
  34. meshagent/cli/webhook.py +7 -7
  35. meshagent/cli/worker.py +1327 -0
  36. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.21.0.dist-info}/METADATA +13 -13
  37. meshagent_cli-0.21.0.dist-info/RECORD +44 -0
  38. meshagent_cli-0.7.0.dist-info/RECORD +0 -36
  39. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.21.0.dist-info}/WHEEL +0 -0
  40. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.21.0.dist-info}/entry_points.txt +0 -0
  41. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.21.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,997 @@
1
+ import json as _json
2
+ from typing import Annotated, Optional, List, Any
3
+ from urllib.parse import urlparse
4
+ from urllib.request import urlopen
5
+
6
+ import typer
7
+ from rich import print
8
+
9
+ from meshagent.api import RequiredTable
10
+ from meshagent.agents.agent import install_required_table
11
+
12
+ from meshagent.cli.common_options import ProjectIdOption, RoomOption
13
+ from meshagent.cli import async_typer
14
+ from meshagent.cli.helper import resolve_project_id, resolve_room, get_client
15
+ from meshagent.api.helpers import meshagent_base_url, websocket_room_url
16
+ from meshagent.api import RoomClient, WebSocketClientProtocol
17
+ from meshagent.api.room_server_client import DataType
18
+ from meshagent.api import RoomException # or wherever you defined it
19
+
20
+ app = async_typer.AsyncTyper(help="Manage database tables in a room")
21
+
22
+
23
+ # ---------------------------
24
+ # Helpers
25
+ # ---------------------------
26
+
27
+
28
+ def _parse_json_arg(json_str: Optional[str], *, name: str) -> Any:
29
+ if json_str is None:
30
+ return None
31
+ try:
32
+ return _json.loads(json_str)
33
+ except Exception as e:
34
+ raise typer.BadParameter(f"Invalid JSON for {name}: {e}")
35
+
36
+
37
+ def _load_json_file(path: Optional[str], *, name: str) -> Any:
38
+ """
39
+ Load JSON from a local file path or an HTTP(S) URL.
40
+ """
41
+ if path is None:
42
+ return None
43
+
44
+ try:
45
+ parsed = urlparse(path)
46
+
47
+ # URL case
48
+ if parsed.scheme in ("http", "https"):
49
+ with urlopen(path) as resp:
50
+ charset = resp.headers.get_content_charset() or "utf-8"
51
+ data = resp.read().decode(charset)
52
+ return _json.loads(data)
53
+
54
+ # Local file case
55
+ with open(path, "r", encoding="utf-8") as f:
56
+ return _json.load(f)
57
+
58
+ except Exception as e:
59
+ raise typer.BadParameter(f"Unable to read {name} from {path}: {e}")
60
+
61
+
62
+ def _ns(namespace: Optional[List[str]]) -> Optional[List[str]]:
63
+ return namespace or None
64
+
65
+
66
+ NamespaceOption = Annotated[
67
+ Optional[List[str]],
68
+ typer.Option(
69
+ "--namespace",
70
+ "-n",
71
+ help="Namespace path segments (repeatable). Example: -n prod -n analytics",
72
+ ),
73
+ ]
74
+
75
+
76
+ # ---------------------------
77
+ # Commands
78
+ # ---------------------------
79
+
80
+
81
+ @app.async_command("tables")
82
+ async def list_tables(
83
+ *,
84
+ project_id: ProjectIdOption,
85
+ room: RoomOption,
86
+ namespace: NamespaceOption = None,
87
+ ):
88
+ account_client = await get_client()
89
+ try:
90
+ project_id = await resolve_project_id(project_id=project_id)
91
+ room_name = resolve_room(room)
92
+ connection = await account_client.connect_room(
93
+ project_id=project_id, room=room_name
94
+ )
95
+
96
+ async with RoomClient(
97
+ protocol=WebSocketClientProtocol(
98
+ url=websocket_room_url(
99
+ room_name=room_name, base_url=meshagent_base_url()
100
+ ),
101
+ token=connection.jwt,
102
+ )
103
+ ) as client:
104
+ tables = await client.database.list_tables(namespace=_ns(namespace))
105
+ if not tables:
106
+ print("[bold yellow]No tables found.[/bold yellow]")
107
+ else:
108
+ for t in tables:
109
+ print(t)
110
+
111
+ except RoomException as e:
112
+ print(f"[red]{e}[/red]")
113
+ raise typer.Exit(1)
114
+ finally:
115
+ await account_client.close()
116
+
117
+
118
+ @app.async_command("inspect")
119
+ async def inspect(
120
+ *,
121
+ project_id: ProjectIdOption,
122
+ room: RoomOption,
123
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
124
+ namespace: NamespaceOption = None,
125
+ json: Annotated[
126
+ bool, typer.Option("--json", help="Output raw schema JSON")
127
+ ] = False,
128
+ ):
129
+ account_client = await get_client()
130
+ try:
131
+ project_id = await resolve_project_id(project_id=project_id)
132
+ room_name = resolve_room(room)
133
+ connection = await account_client.connect_room(
134
+ project_id=project_id, room=room_name
135
+ )
136
+
137
+ async with RoomClient(
138
+ protocol=WebSocketClientProtocol(
139
+ url=websocket_room_url(
140
+ room_name=room_name, base_url=meshagent_base_url()
141
+ ),
142
+ token=connection.jwt,
143
+ )
144
+ ) as client:
145
+ schema = await client.database.inspect(
146
+ table=table, namespace=_ns(namespace)
147
+ )
148
+
149
+ if json:
150
+ # schema values are DataType objects; they expose to_json()
151
+ out = {k: v.to_json() for k, v in schema.items()}
152
+ print(_json.dumps(out, indent=2))
153
+ else:
154
+ print(f"[bold]{table}[/bold]")
155
+ for k, v in schema.items():
156
+ print(f" [cyan]{k}[/cyan]: {v.to_json()}")
157
+
158
+ except RoomException as e:
159
+ print(f"[red]{e}[/red]")
160
+ raise typer.Exit(1)
161
+ finally:
162
+ await account_client.close()
163
+
164
+
165
+ @app.async_command("install")
166
+ async def install_requirements(
167
+ *,
168
+ project_id: ProjectIdOption,
169
+ room: RoomOption,
170
+ file: Annotated[
171
+ Optional[str], typer.Option("--file", help="Path to requirements JSON file")
172
+ ] = None,
173
+ ):
174
+ """
175
+ Create a database from a json file containing a list of RequiredTables.
176
+ """
177
+ account_client = await get_client()
178
+ try:
179
+ project_id = await resolve_project_id(project_id=project_id)
180
+ room_name = resolve_room(room)
181
+ connection = await account_client.connect_room(
182
+ project_id=project_id, room=room_name
183
+ )
184
+
185
+ requirements = _load_json_file(file, name="--file")
186
+
187
+ async with RoomClient(
188
+ protocol=WebSocketClientProtocol(
189
+ url=websocket_room_url(
190
+ room_name=room_name, base_url=meshagent_base_url()
191
+ ),
192
+ token=connection.jwt,
193
+ )
194
+ ) as client:
195
+ for rt in requirements["tables"]:
196
+ rt = RequiredTable.from_json(rt)
197
+ print(f"installing table {rt.name} in namespace {rt.namespace}")
198
+ await install_required_table(room=client, table=rt)
199
+
200
+ except RoomException as e:
201
+ print(f"[red]{e}[/red]")
202
+ raise typer.Exit(1)
203
+ finally:
204
+ await account_client.close()
205
+
206
+
207
+ @app.async_command("create")
208
+ async def create_table(
209
+ *,
210
+ project_id: ProjectIdOption,
211
+ room: RoomOption,
212
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
213
+ mode: Annotated[
214
+ str, typer.Option("--mode", help="create | overwrite | create_if_not_exists")
215
+ ] = "create",
216
+ namespace: NamespaceOption = None,
217
+ schema_json: Annotated[
218
+ Optional[str], typer.Option("--schema-json", help="Schema JSON as a string")
219
+ ] = None,
220
+ schema_file: Annotated[
221
+ Optional[str], typer.Option("--schema-file", help="Path to schema JSON file")
222
+ ] = None,
223
+ data_json: Annotated[
224
+ Optional[str], typer.Option("--data-json", help="Initial rows (JSON list)")
225
+ ] = None,
226
+ data_file: Annotated[
227
+ Optional[str],
228
+ typer.Option("--data-file", help="Path to JSON file with initial rows"),
229
+ ] = None,
230
+ ):
231
+ """
232
+ Create a table with optional schema + optional initial data.
233
+
234
+ Schema JSON format matches your DataType.to_json() structure, e.g.:
235
+ {"id":{"type":"int"}, "body":{"type":"text"}, "embedding":{"type":"vector","size":1536,"element_type":{"type":"float"}}}
236
+ """
237
+ account_client = await get_client()
238
+ try:
239
+ project_id = await resolve_project_id(project_id=project_id)
240
+ room_name = resolve_room(room)
241
+ connection = await account_client.connect_room(
242
+ project_id=project_id, room=room_name
243
+ )
244
+
245
+ schema_obj = _parse_json_arg(schema_json, name="--schema-json")
246
+ schema_obj = (
247
+ schema_obj
248
+ if schema_obj is not None
249
+ else _load_json_file(schema_file, name="--schema-file")
250
+ )
251
+
252
+ data_obj = _parse_json_arg(data_json, name="--data-json")
253
+ data_obj = (
254
+ data_obj
255
+ if data_obj is not None
256
+ else _load_json_file(data_file, name="--data-file")
257
+ )
258
+
259
+ async with RoomClient(
260
+ protocol=WebSocketClientProtocol(
261
+ url=websocket_room_url(
262
+ room_name=room_name, base_url=meshagent_base_url()
263
+ ),
264
+ token=connection.jwt,
265
+ )
266
+ ) as client:
267
+ # Build DataType objects from json if schema provided
268
+ schema = None
269
+ if schema_obj is not None:
270
+ schema = {
271
+ k: DataType.from_json(v) for k, v in schema_obj.items()
272
+ } # hacky but local import-safe
273
+
274
+ if schema is not None:
275
+ await client.database.create_table_with_schema(
276
+ name=table,
277
+ schema=schema,
278
+ data=data_obj,
279
+ mode=mode, # type: ignore
280
+ namespace=_ns(namespace),
281
+ )
282
+ else:
283
+ await client.database.create_table_from_data(
284
+ name=table,
285
+ data=data_obj,
286
+ mode=mode, # type: ignore
287
+ namespace=_ns(namespace),
288
+ )
289
+
290
+ print(f"[bold green]Created table:[/bold green] {table}")
291
+
292
+ except RoomException as e:
293
+ print(f"[red]{e}[/red]")
294
+ raise typer.Exit(1)
295
+ finally:
296
+ await account_client.close()
297
+
298
+
299
+ @app.async_command("drop")
300
+ async def drop_table(
301
+ *,
302
+ project_id: ProjectIdOption,
303
+ room: RoomOption,
304
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
305
+ namespace: NamespaceOption = None,
306
+ ignore_missing: Annotated[
307
+ bool, typer.Option("--ignore-missing", help="Ignore missing table")
308
+ ] = False,
309
+ ):
310
+ account_client = await get_client()
311
+ try:
312
+ project_id = await resolve_project_id(project_id=project_id)
313
+ room_name = resolve_room(room)
314
+ connection = await account_client.connect_room(
315
+ project_id=project_id, room=room_name
316
+ )
317
+
318
+ async with RoomClient(
319
+ protocol=WebSocketClientProtocol(
320
+ url=websocket_room_url(
321
+ room_name=room_name, base_url=meshagent_base_url()
322
+ ),
323
+ token=connection.jwt,
324
+ )
325
+ ) as client:
326
+ await client.database.drop_table(
327
+ name=table, ignore_missing=ignore_missing, namespace=_ns(namespace)
328
+ )
329
+ print(f"[bold green]Dropped table:[/bold green] {table}")
330
+
331
+ except RoomException as e:
332
+ print(f"[red]{e}[/red]")
333
+ raise typer.Exit(1)
334
+ finally:
335
+ await account_client.close()
336
+
337
+
338
+ @app.async_command("add-columns")
339
+ async def add_columns(
340
+ *,
341
+ project_id: ProjectIdOption,
342
+ room: RoomOption,
343
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
344
+ namespace: NamespaceOption = None,
345
+ columns_json: Annotated[
346
+ str, typer.Option(..., "--columns-json", help="JSON object of new columns")
347
+ ] = None,
348
+ ):
349
+ """
350
+ Add columns. JSON supports either:
351
+ - DataType JSON: {"col":{"type":"text"}}
352
+ - or server default SQL expr strings: {"col":"'default'"}
353
+ """
354
+ account_client = await get_client()
355
+ try:
356
+ cols_obj = _parse_json_arg(columns_json, name="--columns-json")
357
+ if not isinstance(cols_obj, dict):
358
+ raise typer.BadParameter("--columns-json must be a JSON object")
359
+
360
+ project_id = await resolve_project_id(project_id=project_id)
361
+ room_name = resolve_room(room)
362
+ connection = await account_client.connect_room(
363
+ project_id=project_id, room=room_name
364
+ )
365
+
366
+ async with RoomClient(
367
+ protocol=WebSocketClientProtocol(
368
+ url=websocket_room_url(
369
+ room_name=room_name, base_url=meshagent_base_url()
370
+ ),
371
+ token=connection.jwt,
372
+ )
373
+ ) as client:
374
+ # Convert DataType json objects into DataType instances; pass strings through.
375
+ new_cols = {}
376
+ for k, v in cols_obj.items():
377
+ if isinstance(v, dict) and "type" in v:
378
+ new_cols[k] = DataType.from_json(v)
379
+ else:
380
+ new_cols[k] = v
381
+
382
+ await client.database.add_columns(
383
+ table=table, new_columns=new_cols, namespace=_ns(namespace)
384
+ )
385
+ print(f"[bold green]Added columns to[/bold green] {table}")
386
+
387
+ except (RoomException, typer.BadParameter) as e:
388
+ print(f"[red]{e}[/red]")
389
+ raise typer.Exit(1)
390
+ finally:
391
+ await account_client.close()
392
+
393
+
394
+ @app.async_command("drop-columns")
395
+ async def drop_columns(
396
+ *,
397
+ project_id: ProjectIdOption,
398
+ room: RoomOption,
399
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
400
+ namespace: NamespaceOption = None,
401
+ columns: Annotated[
402
+ List[str],
403
+ typer.Option(..., "--column", "-c", help="Column to drop (repeatable)"),
404
+ ] = None,
405
+ ):
406
+ account_client = await get_client()
407
+ try:
408
+ project_id = await resolve_project_id(project_id=project_id)
409
+ room_name = resolve_room(room)
410
+ connection = await account_client.connect_room(
411
+ project_id=project_id, room=room_name
412
+ )
413
+
414
+ async with RoomClient(
415
+ protocol=WebSocketClientProtocol(
416
+ url=websocket_room_url(
417
+ room_name=room_name, base_url=meshagent_base_url()
418
+ ),
419
+ token=connection.jwt,
420
+ )
421
+ ) as client:
422
+ await client.database.drop_columns(
423
+ table=table, columns=columns, namespace=_ns(namespace)
424
+ )
425
+ print(f"[bold green]Dropped columns from[/bold green] {table}")
426
+
427
+ except RoomException as e:
428
+ print(f"[red]{e}[/red]")
429
+ raise typer.Exit(1)
430
+ finally:
431
+ await account_client.close()
432
+
433
+
434
+ @app.async_command("insert")
435
+ async def insert(
436
+ *,
437
+ project_id: ProjectIdOption,
438
+ room: RoomOption,
439
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
440
+ namespace: NamespaceOption = None,
441
+ json: Annotated[
442
+ Optional[str], typer.Option("--json", help="JSON list of records")
443
+ ] = None,
444
+ file: Annotated[
445
+ Optional[str],
446
+ typer.Option("--file", "-f", help="Path to JSON file (list of records)"),
447
+ ] = None,
448
+ ):
449
+ account_client = await get_client()
450
+ try:
451
+ records = _parse_json_arg(json, name="--json")
452
+ records = (
453
+ records if records is not None else _load_json_file(file, name="--file")
454
+ )
455
+ if not isinstance(records, list):
456
+ raise typer.BadParameter("insert expects a JSON list of records")
457
+
458
+ project_id = await resolve_project_id(project_id=project_id)
459
+ room_name = resolve_room(room)
460
+ connection = await account_client.connect_room(
461
+ project_id=project_id, room=room_name
462
+ )
463
+
464
+ async with RoomClient(
465
+ protocol=WebSocketClientProtocol(
466
+ url=websocket_room_url(
467
+ room_name=room_name, base_url=meshagent_base_url()
468
+ ),
469
+ token=connection.jwt,
470
+ )
471
+ ) as client:
472
+ await client.database.insert(
473
+ table=table, records=records, namespace=_ns(namespace)
474
+ )
475
+ print(
476
+ f"[bold green]Inserted[/bold green] {len(records)} record(s) into {table}"
477
+ )
478
+
479
+ except (RoomException, typer.BadParameter) as e:
480
+ print(f"[red]{e}[/red]")
481
+ raise typer.Exit(1)
482
+ finally:
483
+ await account_client.close()
484
+
485
+
486
+ @app.async_command("merge")
487
+ async def merge(
488
+ *,
489
+ project_id: ProjectIdOption,
490
+ room: RoomOption,
491
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
492
+ on: Annotated[str, typer.Option(..., "--on", help="Column to match for upsert")],
493
+ namespace: NamespaceOption = None,
494
+ json: Annotated[
495
+ Optional[str], typer.Option("--json", help="JSON records (list)")
496
+ ] = None,
497
+ file: Annotated[
498
+ Optional[str], typer.Option("--file", "-f", help="Path to JSON file (list)")
499
+ ] = None,
500
+ ):
501
+ account_client = await get_client()
502
+ try:
503
+ records = _parse_json_arg(json, name="--json")
504
+ records = (
505
+ records if records is not None else _load_json_file(file, name="--file")
506
+ )
507
+ if not isinstance(records, list):
508
+ raise typer.BadParameter("merge expects a JSON list of records")
509
+
510
+ project_id = await resolve_project_id(project_id=project_id)
511
+ room_name = resolve_room(room)
512
+ connection = await account_client.connect_room(
513
+ project_id=project_id, room=room_name
514
+ )
515
+
516
+ async with RoomClient(
517
+ protocol=WebSocketClientProtocol(
518
+ url=websocket_room_url(
519
+ room_name=room_name, base_url=meshagent_base_url()
520
+ ),
521
+ token=connection.jwt,
522
+ )
523
+ ) as client:
524
+ await client.database.merge(
525
+ table=table, on=on, records=records, namespace=_ns(namespace)
526
+ )
527
+ print(
528
+ f"[bold green]Merged[/bold green] {len(records)} record(s) into {table} on {on}"
529
+ )
530
+
531
+ except (RoomException, typer.BadParameter) as e:
532
+ print(f"[red]{e}[/red]")
533
+ raise typer.Exit(1)
534
+ finally:
535
+ await account_client.close()
536
+
537
+
538
+ @app.async_command("update")
539
+ async def update(
540
+ *,
541
+ project_id: ProjectIdOption,
542
+ room: RoomOption,
543
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
544
+ where: Annotated[
545
+ str, typer.Option(..., "--where", help='SQL WHERE clause, e.g. "id = 1"')
546
+ ],
547
+ namespace: NamespaceOption = None,
548
+ values_json: Annotated[
549
+ Optional[str],
550
+ typer.Option("--values-json", help="JSON object of literal values"),
551
+ ] = None,
552
+ values_sql_json: Annotated[
553
+ Optional[str],
554
+ typer.Option("--values-sql-json", help="JSON object of SQL expressions"),
555
+ ] = None,
556
+ ):
557
+ account_client = await get_client()
558
+ try:
559
+ values = _parse_json_arg(values_json, name="--values-json")
560
+ values_sql = _parse_json_arg(values_sql_json, name="--values-sql-json")
561
+ if values is None and values_sql is None:
562
+ raise typer.BadParameter("Provide --values-json and/or --values-sql-json")
563
+
564
+ project_id = await resolve_project_id(project_id=project_id)
565
+ room_name = resolve_room(room)
566
+ connection = await account_client.connect_room(
567
+ project_id=project_id, room=room_name
568
+ )
569
+
570
+ async with RoomClient(
571
+ protocol=WebSocketClientProtocol(
572
+ url=websocket_room_url(
573
+ room_name=room_name, base_url=meshagent_base_url()
574
+ ),
575
+ token=connection.jwt,
576
+ )
577
+ ) as client:
578
+ await client.database.update(
579
+ table=table,
580
+ where=where,
581
+ values=values,
582
+ values_sql=values_sql,
583
+ namespace=_ns(namespace),
584
+ )
585
+ print(f"[bold green]Updated[/bold green] {table} where {where}")
586
+
587
+ except (RoomException, typer.BadParameter) as e:
588
+ print(f"[red]{e}[/red]")
589
+ raise typer.Exit(1)
590
+ finally:
591
+ await account_client.close()
592
+
593
+
594
+ @app.async_command("delete")
595
+ async def delete(
596
+ *,
597
+ project_id: ProjectIdOption,
598
+ room: RoomOption,
599
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
600
+ where: Annotated[str, typer.Option(..., "--where", help="SQL WHERE clause")],
601
+ namespace: NamespaceOption = None,
602
+ ):
603
+ account_client = await get_client()
604
+ try:
605
+ project_id = await resolve_project_id(project_id=project_id)
606
+ room_name = resolve_room(room)
607
+ connection = await account_client.connect_room(
608
+ project_id=project_id, room=room_name
609
+ )
610
+
611
+ async with RoomClient(
612
+ protocol=WebSocketClientProtocol(
613
+ url=websocket_room_url(
614
+ room_name=room_name, base_url=meshagent_base_url()
615
+ ),
616
+ token=connection.jwt,
617
+ )
618
+ ) as client:
619
+ await client.database.delete(
620
+ table=table, where=where, namespace=_ns(namespace)
621
+ )
622
+ print(f"[bold green]Deleted[/bold green] from {table} where {where}")
623
+
624
+ except RoomException as e:
625
+ print(f"[red]{e}[/red]")
626
+ raise typer.Exit(1)
627
+ finally:
628
+ await account_client.close()
629
+
630
+
631
+ @app.async_command("search")
632
+ async def search(
633
+ *,
634
+ project_id: ProjectIdOption,
635
+ room: RoomOption,
636
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
637
+ namespace: NamespaceOption = None,
638
+ text: Annotated[
639
+ Optional[str], typer.Option("--text", help="Full-text query")
640
+ ] = None,
641
+ vector_json: Annotated[
642
+ Optional[str], typer.Option("--vector-json", help="Vector JSON array")
643
+ ] = None,
644
+ where: Annotated[
645
+ Optional[str], typer.Option("--where", help="SQL WHERE clause")
646
+ ] = None,
647
+ where_json: Annotated[
648
+ Optional[str],
649
+ typer.Option("--where-json", help="JSON object converted to equality ANDs"),
650
+ ] = None,
651
+ select: Annotated[
652
+ Optional[List[str]],
653
+ typer.Option("--select", help="Columns to select (repeatable)"),
654
+ ] = None,
655
+ limit: Annotated[
656
+ Optional[int], typer.Option("--limit", help="Max rows to return")
657
+ ] = None,
658
+ offset: Annotated[
659
+ Optional[int], typer.Option("--offset", help="Rows to skip")
660
+ ] = None,
661
+ pretty: Annotated[
662
+ bool, typer.Option("--pretty/--no-pretty", help="Pretty-print JSON")
663
+ ] = True,
664
+ ):
665
+ account_client = await get_client()
666
+ try:
667
+ vec = _parse_json_arg(vector_json, name="--vector-json")
668
+ if vec is not None and not isinstance(vec, list):
669
+ raise typer.BadParameter("--vector-json must be a JSON array")
670
+ wj = _parse_json_arg(where_json, name="--where-json")
671
+ if wj is not None and not isinstance(wj, dict):
672
+ raise typer.BadParameter("--where-json must be a JSON object")
673
+
674
+ project_id = await resolve_project_id(project_id=project_id)
675
+ room_name = resolve_room(room)
676
+ connection = await account_client.connect_room(
677
+ project_id=project_id, room=room_name
678
+ )
679
+
680
+ async with RoomClient(
681
+ protocol=WebSocketClientProtocol(
682
+ url=websocket_room_url(
683
+ room_name=room_name, base_url=meshagent_base_url()
684
+ ),
685
+ token=connection.jwt,
686
+ )
687
+ ) as client:
688
+ results = await client.database.search(
689
+ table=table,
690
+ text=text,
691
+ vector=vec,
692
+ where=(wj if wj is not None else where),
693
+ select=list(select) if select else None,
694
+ limit=limit,
695
+ offset=offset,
696
+ namespace=_ns(namespace),
697
+ )
698
+ print(_json.dumps(results, indent=2 if pretty else None))
699
+
700
+ except (RoomException, typer.BadParameter) as e:
701
+ print(f"[red]{e}[/red]")
702
+ raise typer.Exit(1)
703
+ finally:
704
+ await account_client.close()
705
+
706
+
707
+ @app.async_command("optimize")
708
+ async def optimize(
709
+ *,
710
+ project_id: ProjectIdOption,
711
+ room: RoomOption,
712
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
713
+ namespace: NamespaceOption = None,
714
+ ):
715
+ account_client = await get_client()
716
+ try:
717
+ project_id = await resolve_project_id(project_id=project_id)
718
+ room_name = resolve_room(room)
719
+ connection = await account_client.connect_room(
720
+ project_id=project_id, room=room_name
721
+ )
722
+
723
+ async with RoomClient(
724
+ protocol=WebSocketClientProtocol(
725
+ url=websocket_room_url(
726
+ room_name=room_name, base_url=meshagent_base_url()
727
+ ),
728
+ token=connection.jwt,
729
+ )
730
+ ) as client:
731
+ await client.database.optimize(table=table, namespace=_ns(namespace))
732
+ print(f"[bold green]Optimized[/bold green] {table}")
733
+
734
+ except RoomException as e:
735
+ print(f"[red]{e}[/red]")
736
+ raise typer.Exit(1)
737
+ finally:
738
+ await account_client.close()
739
+
740
+
741
+ @app.async_command("versions")
742
+ async def list_versions(
743
+ *,
744
+ project_id: ProjectIdOption,
745
+ room: RoomOption,
746
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
747
+ namespace: NamespaceOption = None,
748
+ pretty: Annotated[
749
+ bool, typer.Option("--pretty/--no-pretty", help="Pretty-print JSON")
750
+ ] = True,
751
+ ):
752
+ account_client = await get_client()
753
+ try:
754
+ project_id = await resolve_project_id(project_id=project_id)
755
+ room_name = resolve_room(room)
756
+ connection = await account_client.connect_room(
757
+ project_id=project_id, room=room_name
758
+ )
759
+
760
+ async with RoomClient(
761
+ protocol=WebSocketClientProtocol(
762
+ url=websocket_room_url(
763
+ room_name=room_name, base_url=meshagent_base_url()
764
+ ),
765
+ token=connection.jwt,
766
+ )
767
+ ) as client:
768
+ versions = await client.database.list_versions(
769
+ table=table, namespace=_ns(namespace)
770
+ )
771
+ out = [v.model_dump(mode="json") for v in versions]
772
+ print(_json.dumps(out, indent=2 if pretty else None))
773
+
774
+ except RoomException as e:
775
+ print(f"[red]{e}[/red]")
776
+ raise typer.Exit(1)
777
+ finally:
778
+ await account_client.close()
779
+
780
+
781
+ @app.async_command("checkout")
782
+ async def checkout(
783
+ *,
784
+ project_id: ProjectIdOption,
785
+ room: RoomOption,
786
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
787
+ version: Annotated[int, typer.Option(..., "--version", "-v", help="Table version")],
788
+ namespace: NamespaceOption = None,
789
+ ):
790
+ account_client = await get_client()
791
+ try:
792
+ project_id = await resolve_project_id(project_id=project_id)
793
+ room_name = resolve_room(room)
794
+ connection = await account_client.connect_room(
795
+ project_id=project_id, room=room_name
796
+ )
797
+
798
+ async with RoomClient(
799
+ protocol=WebSocketClientProtocol(
800
+ url=websocket_room_url(
801
+ room_name=room_name, base_url=meshagent_base_url()
802
+ ),
803
+ token=connection.jwt,
804
+ )
805
+ ) as client:
806
+ await client.database.checkout(
807
+ table=table, version=version, namespace=_ns(namespace)
808
+ )
809
+ print(f"[bold green]Checked out[/bold green] {table} @ version {version}")
810
+
811
+ except RoomException as e:
812
+ print(f"[red]{e}[/red]")
813
+ raise typer.Exit(1)
814
+ finally:
815
+ await account_client.close()
816
+
817
+
818
+ @app.async_command("restore")
819
+ async def restore(
820
+ *,
821
+ project_id: ProjectIdOption,
822
+ room: RoomOption,
823
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
824
+ version: Annotated[int, typer.Option(..., "--version", "-v", help="Table version")],
825
+ namespace: NamespaceOption = None,
826
+ ):
827
+ account_client = await get_client()
828
+ try:
829
+ project_id = await resolve_project_id(project_id=project_id)
830
+ room_name = resolve_room(room)
831
+ connection = await account_client.connect_room(
832
+ project_id=project_id, room=room_name
833
+ )
834
+
835
+ async with RoomClient(
836
+ protocol=WebSocketClientProtocol(
837
+ url=websocket_room_url(
838
+ room_name=room_name, base_url=meshagent_base_url()
839
+ ),
840
+ token=connection.jwt,
841
+ )
842
+ ) as client:
843
+ await client.database.restore(
844
+ table=table, version=version, namespace=_ns(namespace)
845
+ )
846
+ print(f"[bold green]Restored[/bold green] {table} to version {version}")
847
+
848
+ except RoomException as e:
849
+ print(f"[red]{e}[/red]")
850
+ raise typer.Exit(1)
851
+ finally:
852
+ await account_client.close()
853
+
854
+
855
+ @app.async_command("indexes")
856
+ async def list_indexes(
857
+ *,
858
+ project_id: ProjectIdOption,
859
+ room: RoomOption,
860
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
861
+ namespace: NamespaceOption = None,
862
+ pretty: Annotated[
863
+ bool, typer.Option("--pretty/--no-pretty", help="Pretty-print JSON")
864
+ ] = True,
865
+ ):
866
+ account_client = await get_client()
867
+ try:
868
+ project_id = await resolve_project_id(project_id=project_id)
869
+ room_name = resolve_room(room)
870
+ connection = await account_client.connect_room(
871
+ project_id=project_id, room=room_name
872
+ )
873
+
874
+ async with RoomClient(
875
+ protocol=WebSocketClientProtocol(
876
+ url=websocket_room_url(
877
+ room_name=room_name, base_url=meshagent_base_url()
878
+ ),
879
+ token=connection.jwt,
880
+ )
881
+ ) as client:
882
+ idxs = await client.database.list_indexes(
883
+ table=table, namespace=_ns(namespace)
884
+ )
885
+ out = [i.model_dump(mode="json") for i in idxs]
886
+ print(_json.dumps(out, indent=2 if pretty else None))
887
+
888
+ except RoomException as e:
889
+ print(f"[red]{e}[/red]")
890
+ raise typer.Exit(1)
891
+ finally:
892
+ await account_client.close()
893
+
894
+
895
+ @app.async_command("index-create")
896
+ async def create_index(
897
+ *,
898
+ project_id: ProjectIdOption,
899
+ room: RoomOption,
900
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
901
+ column: Annotated[str, typer.Option(..., "--column", "-c", help="Column name")],
902
+ kind: Annotated[
903
+ str, typer.Option(..., "--kind", help="vector | scalar | fts")
904
+ ] = "scalar",
905
+ replace: Annotated[
906
+ Optional[bool],
907
+ typer.Option(
908
+ "--replace/--no-replace",
909
+ help="Replace existing index if it already exists",
910
+ ),
911
+ ] = None,
912
+ namespace: NamespaceOption = None,
913
+ ):
914
+ account_client = await get_client()
915
+ try:
916
+ project_id = await resolve_project_id(project_id=project_id)
917
+ room_name = resolve_room(room)
918
+ connection = await account_client.connect_room(
919
+ project_id=project_id, room=room_name
920
+ )
921
+
922
+ async with RoomClient(
923
+ protocol=WebSocketClientProtocol(
924
+ url=websocket_room_url(
925
+ room_name=room_name, base_url=meshagent_base_url()
926
+ ),
927
+ token=connection.jwt,
928
+ )
929
+ ) as client:
930
+ if kind == "vector":
931
+ await client.database.create_vector_index(
932
+ table=table,
933
+ column=column,
934
+ replace=replace,
935
+ namespace=_ns(namespace),
936
+ )
937
+ elif kind == "scalar":
938
+ await client.database.create_scalar_index(
939
+ table=table,
940
+ column=column,
941
+ replace=replace,
942
+ namespace=_ns(namespace),
943
+ )
944
+ elif kind in ("fts", "full_text", "full-text"):
945
+ await client.database.create_full_text_search_index(
946
+ table=table,
947
+ column=column,
948
+ replace=replace,
949
+ namespace=_ns(namespace),
950
+ )
951
+ else:
952
+ raise typer.BadParameter("--kind must be one of: vector, scalar, fts")
953
+
954
+ print(f"[bold green]Created[/bold green] {kind} index on {table}.{column}")
955
+
956
+ except (RoomException, typer.BadParameter) as e:
957
+ print(f"[red]{e}[/red]")
958
+ raise typer.Exit(1)
959
+ finally:
960
+ await account_client.close()
961
+
962
+
963
+ @app.async_command("index-drop")
964
+ async def drop_index(
965
+ *,
966
+ project_id: ProjectIdOption,
967
+ room: RoomOption,
968
+ table: Annotated[str, typer.Option(..., "--table", "-t", help="Table name")],
969
+ name: Annotated[str, typer.Option(..., "--name", help="Index name")],
970
+ namespace: NamespaceOption = None,
971
+ ):
972
+ account_client = await get_client()
973
+ try:
974
+ project_id = await resolve_project_id(project_id=project_id)
975
+ room_name = resolve_room(room)
976
+ connection = await account_client.connect_room(
977
+ project_id=project_id, room=room_name
978
+ )
979
+
980
+ async with RoomClient(
981
+ protocol=WebSocketClientProtocol(
982
+ url=websocket_room_url(
983
+ room_name=room_name, base_url=meshagent_base_url()
984
+ ),
985
+ token=connection.jwt,
986
+ )
987
+ ) as client:
988
+ await client.database.drop_index(
989
+ table=table, name=name, namespace=_ns(namespace)
990
+ )
991
+ print(f"[bold green]Dropped index[/bold green] {name} on {table}")
992
+
993
+ except RoomException as e:
994
+ print(f"[red]{e}[/red]")
995
+ raise typer.Exit(1)
996
+ finally:
997
+ await account_client.close()