sovaria-cli 0.2.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 (46) hide show
  1. apps/cli/README.md +147 -0
  2. apps/cli/__init__.py +0 -0
  3. apps/cli/src/__init__.py +3 -0
  4. apps/cli/src/commands/__init__.py +1 -0
  5. apps/cli/src/commands/admin.py +160 -0
  6. apps/cli/src/commands/api.py +48 -0
  7. apps/cli/src/commands/audit.py +181 -0
  8. apps/cli/src/commands/auth_product.py +187 -0
  9. apps/cli/src/commands/billing.py +513 -0
  10. apps/cli/src/commands/chrono.py +248 -0
  11. apps/cli/src/commands/context.py +99 -0
  12. apps/cli/src/commands/data.py +272 -0
  13. apps/cli/src/commands/db.py +528 -0
  14. apps/cli/src/commands/deploy.py +1099 -0
  15. apps/cli/src/commands/governance.py +491 -0
  16. apps/cli/src/commands/iam.py +436 -0
  17. apps/cli/src/commands/init_cmd.py +177 -0
  18. apps/cli/src/commands/keys.py +120 -0
  19. apps/cli/src/commands/login.py +842 -0
  20. apps/cli/src/commands/org.py +157 -0
  21. apps/cli/src/commands/policy.py +317 -0
  22. apps/cli/src/commands/project.py +130 -0
  23. apps/cli/src/commands/realtime.py +364 -0
  24. apps/cli/src/commands/scope.py +42 -0
  25. apps/cli/src/commands/shell.py +415 -0
  26. apps/cli/src/commands/sim.py +178 -0
  27. apps/cli/src/commands/storage.py +405 -0
  28. apps/cli/src/commands/vector.py +204 -0
  29. apps/cli/src/main.py +169 -0
  30. apps/cli/src/utils/__init__.py +1 -0
  31. apps/cli/src/utils/api_client.py +329 -0
  32. apps/cli/src/utils/auth_store.py +299 -0
  33. apps/cli/src/utils/config.py +185 -0
  34. apps/cli/src/utils/display.py +62 -0
  35. apps/cli/src/utils/migrations.py +69 -0
  36. apps/cli/src/utils/project_context.py +124 -0
  37. apps/cli/tests/test_auth_store_profiles.py +103 -0
  38. apps/cli/tests/test_cli_security.py +110 -0
  39. apps/cli/tests/test_cli_smoke_paths.py +279 -0
  40. apps/cli/tests/test_config_profiles.py +55 -0
  41. apps/cli/tests/test_main_startup.py +29 -0
  42. apps/cli/tests/test_project_and_commands.py +196 -0
  43. sovaria_cli-0.2.0.dist-info/METADATA +170 -0
  44. sovaria_cli-0.2.0.dist-info/RECORD +46 -0
  45. sovaria_cli-0.2.0.dist-info/WHEEL +4 -0
  46. sovaria_cli-0.2.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,405 @@
1
+ """
2
+ apps.cli.src.commands.storage — Vault storage management commands.
3
+
4
+ Commands:
5
+ sov storage buckets List all buckets
6
+ sov storage create-bucket Create a new bucket
7
+ sov storage ls List objects in a bucket
8
+ sov storage upload Upload a file to a bucket
9
+ sov storage download Download an object
10
+ sov storage sign-download Mint a signed download URL
11
+ sov storage sign-upload Mint a signed upload URL
12
+ sov storage rm Delete an object
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from pathlib import Path
18
+ from urllib.parse import quote
19
+
20
+ import httpx
21
+ import typer
22
+
23
+ from apps.cli.src.utils.api_client import CLIRequestError, build_request_context, call_api
24
+ from apps.cli.src.utils.display import print_json, print_success
25
+
26
+ app = typer.Typer(help="Object storage management")
27
+
28
+
29
+ def _object_path(bucket: str, object_path: str) -> str:
30
+ encoded = "/".join(quote(part, safe="") for part in object_path.strip("/").split("/"))
31
+ return f"/api/v1/store/objects/{quote(bucket, safe='')}/{encoded}"
32
+
33
+
34
+ @app.command("buckets")
35
+ def list_buckets(
36
+ project_id: str | None = typer.Option(None, "--project", help="Project ID"),
37
+ org_id: str | None = typer.Option(None, "--org"),
38
+ env_id: str | None = typer.Option(None, "--env"),
39
+ profile: str | None = typer.Option(None, "--profile"),
40
+ ) -> None:
41
+ """List all storage buckets."""
42
+ try:
43
+ result = call_api(
44
+ method="GET",
45
+ path="/api/v1/store/buckets",
46
+ profile=profile,
47
+ org_id=org_id,
48
+ project_id=project_id,
49
+ env_id=env_id,
50
+ require_scope=True,
51
+ )
52
+ except CLIRequestError as exc:
53
+ typer.echo(str(exc), err=True)
54
+ raise typer.Exit(1) from exc
55
+
56
+ if not isinstance(result, list):
57
+ print_json(result)
58
+ return
59
+ for bucket in result:
60
+ vis = "public" if bucket.get("public") else "private"
61
+ typer.echo(f" {bucket['name']} ({vis}) objects={bucket.get('object_count', 0)}")
62
+
63
+
64
+ @app.command("create-bucket")
65
+ def create_bucket(
66
+ name: str = typer.Argument(..., help="Bucket name"),
67
+ project_id: str | None = typer.Option(None, "--project", help="Project ID"),
68
+ org_id: str | None = typer.Option(None, "--org"),
69
+ env_id: str | None = typer.Option(None, "--env"),
70
+ public: bool = typer.Option(False, "--public", help="Make bucket public"),
71
+ profile: str | None = typer.Option(None, "--profile"),
72
+ ) -> None:
73
+ """Create a new storage bucket."""
74
+ try:
75
+ call_api(
76
+ method="POST",
77
+ path="/api/v1/store/buckets",
78
+ profile=profile,
79
+ org_id=org_id,
80
+ project_id=project_id,
81
+ env_id=env_id,
82
+ require_scope=True,
83
+ body={"name": name, "public": public},
84
+ )
85
+ except CLIRequestError as exc:
86
+ typer.echo(str(exc), err=True)
87
+ raise typer.Exit(1) from exc
88
+ print_success(f"Bucket '{name}' created")
89
+
90
+
91
+ @app.command("ls")
92
+ def list_objects(
93
+ bucket: str = typer.Argument(..., help="Bucket name"),
94
+ project_id: str | None = typer.Option(None, "--project", help="Project ID"),
95
+ org_id: str | None = typer.Option(None, "--org"),
96
+ env_id: str | None = typer.Option(None, "--env"),
97
+ prefix: str = typer.Option("", "--prefix", help="Filter by prefix"),
98
+ profile: str | None = typer.Option(None, "--profile"),
99
+ ) -> None:
100
+ """List objects in a bucket."""
101
+ params = {"prefix": prefix} if prefix else None
102
+ try:
103
+ result = call_api(
104
+ method="GET",
105
+ path=f"/api/v1/store/objects/{quote(bucket, safe='')}",
106
+ profile=profile,
107
+ org_id=org_id,
108
+ project_id=project_id,
109
+ env_id=env_id,
110
+ require_scope=True,
111
+ params=params,
112
+ )
113
+ except CLIRequestError as exc:
114
+ typer.echo(str(exc), err=True)
115
+ raise typer.Exit(1) from exc
116
+
117
+ if not isinstance(result, list):
118
+ print_json(result)
119
+ return
120
+ for obj in result:
121
+ size_kb = obj.get("size", 0) / 1024
122
+ typer.echo(f" {obj['name']} {size_kb:.1f}KB {obj.get('content_type', '-')}")
123
+
124
+
125
+ @app.command("upload")
126
+ def upload(
127
+ bucket: str = typer.Argument(..., help="Bucket name"),
128
+ file_path: str = typer.Argument(..., help="Local file path"),
129
+ project_id: str | None = typer.Option(None, "--project", help="Project ID"),
130
+ org_id: str | None = typer.Option(None, "--org"),
131
+ env_id: str | None = typer.Option(None, "--env"),
132
+ object_path: str | None = typer.Option(None, "--name", help="Remote object path (defaults to filename)"),
133
+ profile: str | None = typer.Option(None, "--profile"),
134
+ ) -> None:
135
+ """Upload a file to a bucket."""
136
+ path = Path(file_path)
137
+ if not path.exists():
138
+ typer.echo(f"File not found: {file_path}", err=True)
139
+ raise typer.Exit(1)
140
+ remote_name = object_path or path.name
141
+ try:
142
+ api_url, headers = build_request_context(
143
+ profile=profile,
144
+ org_id=org_id,
145
+ project_id=project_id,
146
+ env_id=env_id,
147
+ require_scope=True,
148
+ )
149
+ with open(path, "rb") as handle:
150
+ response = httpx.post(
151
+ f"{api_url}{_object_path(bucket, remote_name)}",
152
+ headers=headers,
153
+ files={"file": (remote_name, handle)},
154
+ timeout=120,
155
+ )
156
+ response.raise_for_status()
157
+ except CLIRequestError as exc:
158
+ typer.echo(str(exc), err=True)
159
+ raise typer.Exit(1) from exc
160
+ except httpx.HTTPStatusError as exc:
161
+ typer.echo(f"Error: {exc.response.text}", err=True)
162
+ raise typer.Exit(1) from exc
163
+ print_success(f"Uploaded: {remote_name} -> {bucket}")
164
+
165
+
166
+ @app.command("download")
167
+ def download(
168
+ bucket: str = typer.Argument(..., help="Bucket name"),
169
+ object_path: str = typer.Argument(..., help="Remote object path"),
170
+ output: Path = typer.Option(..., "--output", "-o", help="Local output file path"),
171
+ project_id: str | None = typer.Option(None, "--project"),
172
+ org_id: str | None = typer.Option(None, "--org"),
173
+ env_id: str | None = typer.Option(None, "--env"),
174
+ profile: str | None = typer.Option(None, "--profile"),
175
+ ) -> None:
176
+ """Download an object to a local file."""
177
+ try:
178
+ api_url, headers = build_request_context(
179
+ profile=profile,
180
+ org_id=org_id,
181
+ project_id=project_id,
182
+ env_id=env_id,
183
+ require_scope=True,
184
+ )
185
+ response = httpx.get(
186
+ f"{api_url}{_object_path(bucket, object_path)}",
187
+ headers=headers,
188
+ timeout=120,
189
+ )
190
+ response.raise_for_status()
191
+ output.parent.mkdir(parents=True, exist_ok=True)
192
+ output.write_bytes(response.content)
193
+ except CLIRequestError as exc:
194
+ typer.echo(str(exc), err=True)
195
+ raise typer.Exit(1) from exc
196
+ except httpx.HTTPStatusError as exc:
197
+ typer.echo(f"Error: {exc.response.text}", err=True)
198
+ raise typer.Exit(1) from exc
199
+ print_success(f"Downloaded to {output}")
200
+
201
+
202
+ @app.command("sign-download")
203
+ def sign_download(
204
+ bucket: str = typer.Argument(...),
205
+ object_path: str = typer.Argument(...),
206
+ expires_in: int = typer.Option(3600, "--expires-in", help="TTL seconds"),
207
+ project_id: str | None = typer.Option(None, "--project"),
208
+ org_id: str | None = typer.Option(None, "--org"),
209
+ env_id: str | None = typer.Option(None, "--env"),
210
+ profile: str | None = typer.Option(None, "--profile"),
211
+ ) -> None:
212
+ """Mint a time-limited signed download URL."""
213
+ try:
214
+ result = call_api(
215
+ method="POST",
216
+ path=f"{_object_path(bucket, object_path)}/sign-download",
217
+ profile=profile,
218
+ org_id=org_id,
219
+ project_id=project_id,
220
+ env_id=env_id,
221
+ require_scope=True,
222
+ body={"expires_in": expires_in},
223
+ )
224
+ except CLIRequestError as exc:
225
+ typer.echo(str(exc), err=True)
226
+ raise typer.Exit(1) from exc
227
+ print_json(result)
228
+
229
+
230
+ @app.command("sign-upload")
231
+ def sign_upload(
232
+ bucket: str = typer.Argument(...),
233
+ object_path: str = typer.Argument(...),
234
+ expires_in: int = typer.Option(3600, "--expires-in", help="TTL seconds"),
235
+ project_id: str | None = typer.Option(None, "--project"),
236
+ org_id: str | None = typer.Option(None, "--org"),
237
+ env_id: str | None = typer.Option(None, "--env"),
238
+ profile: str | None = typer.Option(None, "--profile"),
239
+ ) -> None:
240
+ """Mint a time-limited signed upload URL."""
241
+ try:
242
+ result = call_api(
243
+ method="POST",
244
+ path=f"{_object_path(bucket, object_path)}/sign-upload",
245
+ profile=profile,
246
+ org_id=org_id,
247
+ project_id=project_id,
248
+ env_id=env_id,
249
+ require_scope=True,
250
+ body={"expires_in": expires_in},
251
+ )
252
+ except CLIRequestError as exc:
253
+ typer.echo(str(exc), err=True)
254
+ raise typer.Exit(1) from exc
255
+ print_json(result)
256
+
257
+
258
+ @app.command("rm")
259
+ def delete_object(
260
+ bucket: str = typer.Argument(..., help="Bucket name"),
261
+ object_path: str = typer.Argument(..., help="Object path to delete"),
262
+ project_id: str | None = typer.Option(None, "--project", help="Project ID"),
263
+ org_id: str | None = typer.Option(None, "--org"),
264
+ env_id: str | None = typer.Option(None, "--env"),
265
+ profile: str | None = typer.Option(None, "--profile"),
266
+ ) -> None:
267
+ """Delete an object from a bucket."""
268
+ try:
269
+ call_api(
270
+ method="DELETE",
271
+ path=_object_path(bucket, object_path),
272
+ profile=profile,
273
+ org_id=org_id,
274
+ project_id=project_id,
275
+ env_id=env_id,
276
+ require_scope=True,
277
+ )
278
+ except CLIRequestError as exc:
279
+ typer.echo(str(exc), err=True)
280
+ raise typer.Exit(1) from exc
281
+ print_success(f"Deleted: {bucket}/{object_path}")
282
+
283
+
284
+ @app.command("bucket-get")
285
+ def bucket_get(
286
+ name: str = typer.Argument(..., help="Bucket name"),
287
+ project_id: str | None = typer.Option(None, "--project"),
288
+ org_id: str | None = typer.Option(None, "--org"),
289
+ env_id: str | None = typer.Option(None, "--env"),
290
+ profile: str | None = typer.Option(None, "--profile"),
291
+ ) -> None:
292
+ """Fetch bucket metadata."""
293
+ try:
294
+ result = call_api(
295
+ method="GET",
296
+ path=f"/api/v1/store/buckets/{quote(name, safe='')}",
297
+ profile=profile,
298
+ org_id=org_id,
299
+ project_id=project_id,
300
+ env_id=env_id,
301
+ require_scope=True,
302
+ )
303
+ except CLIRequestError as exc:
304
+ typer.echo(str(exc), err=True)
305
+ raise typer.Exit(1) from exc
306
+ print_json(result)
307
+
308
+
309
+ @app.command("bucket-update")
310
+ def bucket_update(
311
+ name: str = typer.Argument(..., help="Bucket name"),
312
+ public: bool | None = typer.Option(None, "--public/--private"),
313
+ file_size_limit: int | None = typer.Option(None, "--file-size-limit"),
314
+ allowed_mime_types: str | None = typer.Option(
315
+ None, "--allowed-mime-types", help="Comma-separated MIME patterns"
316
+ ),
317
+ project_id: str | None = typer.Option(None, "--project"),
318
+ org_id: str | None = typer.Option(None, "--org"),
319
+ env_id: str | None = typer.Option(None, "--env"),
320
+ profile: str | None = typer.Option(None, "--profile"),
321
+ ) -> None:
322
+ """Update bucket settings."""
323
+ body: dict[str, object] = {}
324
+ if public is not None:
325
+ body["public"] = public
326
+ if file_size_limit is not None:
327
+ body["file_size_limit"] = file_size_limit
328
+ if allowed_mime_types is not None:
329
+ body["allowed_mime_types"] = [
330
+ part.strip() for part in allowed_mime_types.split(",") if part.strip()
331
+ ]
332
+ if not body:
333
+ raise typer.BadParameter("Provide at least one field to update")
334
+ try:
335
+ result = call_api(
336
+ method="PATCH",
337
+ path=f"/api/v1/store/buckets/{quote(name, safe='')}",
338
+ profile=profile,
339
+ org_id=org_id,
340
+ project_id=project_id,
341
+ env_id=env_id,
342
+ require_scope=True,
343
+ body=body,
344
+ )
345
+ except CLIRequestError as exc:
346
+ typer.echo(str(exc), err=True)
347
+ raise typer.Exit(1) from exc
348
+ print_json(result)
349
+
350
+
351
+ @app.command("bucket-delete")
352
+ def bucket_delete(
353
+ name: str = typer.Argument(..., help="Bucket name"),
354
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
355
+ project_id: str | None = typer.Option(None, "--project"),
356
+ org_id: str | None = typer.Option(None, "--org"),
357
+ env_id: str | None = typer.Option(None, "--env"),
358
+ profile: str | None = typer.Option(None, "--profile"),
359
+ ) -> None:
360
+ """Delete a storage bucket and all objects."""
361
+ if not yes and not typer.confirm(f"Delete bucket '{name}' and all objects?"):
362
+ raise typer.Exit(0)
363
+ try:
364
+ result = call_api(
365
+ method="DELETE",
366
+ path=f"/api/v1/store/buckets/{quote(name, safe='')}",
367
+ profile=profile,
368
+ org_id=org_id,
369
+ project_id=project_id,
370
+ env_id=env_id,
371
+ require_scope=True,
372
+ )
373
+ except CLIRequestError as exc:
374
+ typer.echo(str(exc), err=True)
375
+ raise typer.Exit(1) from exc
376
+ print_json(result)
377
+ print_success(f"Deleted bucket '{name}'")
378
+
379
+
380
+ @app.command("move")
381
+ def move_object(
382
+ from_path: str = typer.Option(..., "--from", help="Source path bucket/object"),
383
+ to_path: str = typer.Option(..., "--to", help="Destination path bucket/object"),
384
+ project_id: str | None = typer.Option(None, "--project"),
385
+ org_id: str | None = typer.Option(None, "--org"),
386
+ env_id: str | None = typer.Option(None, "--env"),
387
+ profile: str | None = typer.Option(None, "--profile"),
388
+ ) -> None:
389
+ """Move or rename an object within storage."""
390
+ try:
391
+ result = call_api(
392
+ method="POST",
393
+ path="/api/v1/store/objects/move",
394
+ profile=profile,
395
+ org_id=org_id,
396
+ project_id=project_id,
397
+ env_id=env_id,
398
+ require_scope=True,
399
+ body={"from_path": from_path, "to_path": to_path},
400
+ )
401
+ except CLIRequestError as exc:
402
+ typer.echo(str(exc), err=True)
403
+ raise typer.Exit(1) from exc
404
+ print_json(result)
405
+ print_success(f"Moved {from_path} -> {to_path}")
@@ -0,0 +1,204 @@
1
+ """Nexus vector / embedding commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from urllib.parse import quote
6
+
7
+ import typer
8
+
9
+ from apps.cli.src.utils.api_client import CLIRequestError, call_api, parse_json_arg, print_json
10
+
11
+ app = typer.Typer(help="Vector search and embeddings (Nexus engine)")
12
+
13
+
14
+ @app.command("embed")
15
+ def embed(
16
+ text: list[str] = typer.Argument(..., help="One or more texts to embed"),
17
+ org_id: str | None = typer.Option(None, "--org"),
18
+ project_id: str | None = typer.Option(None, "--project"),
19
+ env_id: str | None = typer.Option(None, "--env"),
20
+ profile: str | None = typer.Option(None, "--profile"),
21
+ ) -> None:
22
+ """Generate embeddings for text inputs."""
23
+ try:
24
+ result = call_api(
25
+ method="POST",
26
+ path="/api/v1/embed",
27
+ profile=profile,
28
+ org_id=org_id,
29
+ project_id=project_id,
30
+ env_id=env_id,
31
+ require_scope=True,
32
+ body={"texts": text},
33
+ )
34
+ except CLIRequestError as exc:
35
+ typer.echo(str(exc), err=True)
36
+ raise typer.Exit(1) from exc
37
+ typer.echo(print_json(result))
38
+
39
+
40
+ @app.command("upsert")
41
+ def upsert(
42
+ collection: str = typer.Argument(..., help="Collection name"),
43
+ docs_json: str = typer.Option(..., "--docs", help='JSON array: [{"text":"...", "id":"..."}]'),
44
+ org_id: str | None = typer.Option(None, "--org"),
45
+ project_id: str | None = typer.Option(None, "--project"),
46
+ env_id: str | None = typer.Option(None, "--env"),
47
+ profile: str | None = typer.Option(None, "--profile"),
48
+ ) -> None:
49
+ """Embed and upsert documents into a vector collection."""
50
+ docs = parse_json_arg(docs_json, label="--docs")
51
+ try:
52
+ result = call_api(
53
+ method="POST",
54
+ path="/api/v1/vectors/upsert",
55
+ profile=profile,
56
+ org_id=org_id,
57
+ project_id=project_id,
58
+ env_id=env_id,
59
+ require_scope=True,
60
+ body={"collection": collection, "docs": docs},
61
+ )
62
+ except CLIRequestError as exc:
63
+ typer.echo(str(exc), err=True)
64
+ raise typer.Exit(1) from exc
65
+ typer.echo(print_json(result))
66
+
67
+
68
+ @app.command("search")
69
+ def search(
70
+ collection: str = typer.Argument(..., help="Collection name"),
71
+ query: str = typer.Argument(..., help="Search query text"),
72
+ top_k: int = typer.Option(5, "--top-k", min=1, max=100),
73
+ org_id: str | None = typer.Option(None, "--org"),
74
+ project_id: str | None = typer.Option(None, "--project"),
75
+ env_id: str | None = typer.Option(None, "--env"),
76
+ profile: str | None = typer.Option(None, "--profile"),
77
+ ) -> None:
78
+ """Semantic search within a vector collection."""
79
+ try:
80
+ result = call_api(
81
+ method="POST",
82
+ path="/api/v1/vectors/search",
83
+ profile=profile,
84
+ org_id=org_id,
85
+ project_id=project_id,
86
+ env_id=env_id,
87
+ require_scope=True,
88
+ body={"collection": collection, "query": query, "top_k": top_k, "metadata_filter": {}},
89
+ )
90
+ except CLIRequestError as exc:
91
+ typer.echo(str(exc), err=True)
92
+ raise typer.Exit(1) from exc
93
+ typer.echo(print_json(result))
94
+
95
+
96
+ @app.command("collections")
97
+ def collections(
98
+ org_id: str | None = typer.Option(None, "--org"),
99
+ project_id: str | None = typer.Option(None, "--project"),
100
+ env_id: str | None = typer.Option(None, "--env"),
101
+ profile: str | None = typer.Option(None, "--profile"),
102
+ ) -> None:
103
+ """List vector collections in the current project scope."""
104
+ try:
105
+ result = call_api(
106
+ method="GET",
107
+ path="/api/v1/collections",
108
+ profile=profile,
109
+ org_id=org_id,
110
+ project_id=project_id,
111
+ env_id=env_id,
112
+ require_scope=True,
113
+ )
114
+ except CLIRequestError as exc:
115
+ typer.echo(str(exc), err=True)
116
+ raise typer.Exit(1) from exc
117
+ typer.echo(print_json(result))
118
+
119
+
120
+ @app.command("records")
121
+ def records(
122
+ collection: str = typer.Argument(..., help="Collection name"),
123
+ limit: int | None = typer.Option(None, "--limit"),
124
+ offset: int | None = typer.Option(None, "--offset"),
125
+ org_id: str | None = typer.Option(None, "--org"),
126
+ project_id: str | None = typer.Option(None, "--project"),
127
+ env_id: str | None = typer.Option(None, "--env"),
128
+ profile: str | None = typer.Option(None, "--profile"),
129
+ ) -> None:
130
+ """List records in a vector collection."""
131
+ params: dict[str, str] = {}
132
+ if limit is not None:
133
+ params["limit"] = str(limit)
134
+ if offset is not None:
135
+ params["offset"] = str(offset)
136
+ try:
137
+ result = call_api(
138
+ method="GET",
139
+ path=f"/api/v1/collections/{quote(collection, safe='')}/records",
140
+ profile=profile,
141
+ org_id=org_id,
142
+ project_id=project_id,
143
+ env_id=env_id,
144
+ require_scope=True,
145
+ params=params or None,
146
+ )
147
+ except CLIRequestError as exc:
148
+ typer.echo(str(exc), err=True)
149
+ raise typer.Exit(1) from exc
150
+ typer.echo(print_json(result))
151
+
152
+
153
+ @app.command("delete-record")
154
+ def delete_record(
155
+ collection: str = typer.Argument(..., help="Collection name"),
156
+ record_id: str = typer.Argument(..., help="Record ID"),
157
+ org_id: str | None = typer.Option(None, "--org"),
158
+ project_id: str | None = typer.Option(None, "--project"),
159
+ env_id: str | None = typer.Option(None, "--env"),
160
+ profile: str | None = typer.Option(None, "--profile"),
161
+ ) -> None:
162
+ """Delete one record from a vector collection."""
163
+ try:
164
+ result = call_api(
165
+ method="DELETE",
166
+ path=(
167
+ f"/api/v1/collections/{quote(collection, safe='')}/records/"
168
+ f"{quote(record_id, safe='')}"
169
+ ),
170
+ profile=profile,
171
+ org_id=org_id,
172
+ project_id=project_id,
173
+ env_id=env_id,
174
+ require_scope=True,
175
+ )
176
+ except CLIRequestError as exc:
177
+ typer.echo(str(exc), err=True)
178
+ raise typer.Exit(1) from exc
179
+ typer.echo(print_json(result))
180
+
181
+
182
+ @app.command("delete-collection")
183
+ def delete_collection(
184
+ collection: str = typer.Argument(..., help="Collection name"),
185
+ org_id: str | None = typer.Option(None, "--org"),
186
+ project_id: str | None = typer.Option(None, "--project"),
187
+ env_id: str | None = typer.Option(None, "--env"),
188
+ profile: str | None = typer.Option(None, "--profile"),
189
+ ) -> None:
190
+ """Delete an entire vector collection."""
191
+ try:
192
+ result = call_api(
193
+ method="DELETE",
194
+ path=f"/api/v1/collections/{quote(collection, safe='')}",
195
+ profile=profile,
196
+ org_id=org_id,
197
+ project_id=project_id,
198
+ env_id=env_id,
199
+ require_scope=True,
200
+ )
201
+ except CLIRequestError as exc:
202
+ typer.echo(str(exc), err=True)
203
+ raise typer.Exit(1) from exc
204
+ typer.echo(print_json(result))