embed-client-dev 4.0.78__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 (48) hide show
  1. embed_client/__init__.py +36 -0
  2. embed_client/__main__.py +15 -0
  3. embed_client/_version.py +3 -0
  4. embed_client/adapter_config_factory.py +213 -0
  5. embed_client/adapter_config_normalizer.py +135 -0
  6. embed_client/adapter_generator_bridge.py +166 -0
  7. embed_client/auth.py +189 -0
  8. embed_client/cli.py +483 -0
  9. embed_client/cli_core.py +427 -0
  10. embed_client/client_factory.py +515 -0
  11. embed_client/client_factory_examples.py +375 -0
  12. embed_client/client_factory_helpers.py +59 -0
  13. embed_client/config.py +408 -0
  14. embed_client/config_defaults.py +77 -0
  15. embed_client/config_examples.py +228 -0
  16. embed_client/config_generator.py +652 -0
  17. embed_client/config_generator_cli.py +151 -0
  18. embed_client/config_presets.py +245 -0
  19. embed_client/config_validator.py +246 -0
  20. embed_client/config_validator_cli.py +141 -0
  21. embed_client/constants.py +52 -0
  22. embed_client/demo_cli.py +78 -0
  23. embed_client/demo_security_cli.py +206 -0
  24. embed_client/embed_file_output_validate.py +348 -0
  25. embed_client/embed_file_status_store.py +80 -0
  26. embed_client/embedding_client.py +1929 -0
  27. embed_client/embedding_client_helpers.py +105 -0
  28. embed_client/example_async_usage.py +357 -0
  29. embed_client/example_async_usage_demo.py +282 -0
  30. embed_client/example_async_usage_ru.py +340 -0
  31. embed_client/example_async_usage_ru_demo.py +313 -0
  32. embed_client/exceptions.py +122 -0
  33. embed_client/identifier_types.py +56 -0
  34. embed_client/json_embed_file_preflight.py +128 -0
  35. embed_client/local_json_embed_file.py +73 -0
  36. embed_client/md_embed_file_prep.py +197 -0
  37. embed_client/response_normalizer.py +160 -0
  38. embed_client/response_parsers.py +219 -0
  39. embed_client/security_cli.py +200 -0
  40. embed_client/security_cli_core.py +227 -0
  41. embed_client/ssl_examples.py +278 -0
  42. embed_client/ssl_manager.py +141 -0
  43. embed_client_dev-4.0.78.dist-info/METADATA +155 -0
  44. embed_client_dev-4.0.78.dist-info/RECORD +48 -0
  45. embed_client_dev-4.0.78.dist-info/WHEEL +5 -0
  46. embed_client_dev-4.0.78.dist-info/entry_points.txt +4 -0
  47. embed_client_dev-4.0.78.dist-info/licenses/LICENSE +21 -0
  48. embed_client_dev-4.0.78.dist-info/top_level.txt +1 -0
embed_client/auth.py ADDED
@@ -0,0 +1,189 @@
1
+ """
2
+ Authentication system for embed-client.
3
+
4
+ This module provides authentication configuration validation for the embed-client.
5
+ All actual authentication is handled by mcp_proxy_adapter JsonRpcClient,
6
+ which uses mcp_security_framework internally.
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ import base64
13
+ import logging
14
+ from typing import Any, Dict, List, Optional
15
+
16
+
17
+ class AuthenticationError(Exception):
18
+ """Raised when authentication fails."""
19
+
20
+ def __init__(self, message: str, error_code: int = 401):
21
+ self.message = message
22
+ self.error_code = error_code
23
+ super().__init__(self.message)
24
+
25
+
26
+ class AuthResult:
27
+ """Authentication result container."""
28
+
29
+ def __init__(
30
+ self,
31
+ success: bool,
32
+ user_id: Optional[str] = None,
33
+ roles: Optional[List[str]] = None,
34
+ error: Optional[str] = None,
35
+ ):
36
+ self.success = success
37
+ self.user_id = user_id
38
+ self.roles = roles or []
39
+ self.error = error
40
+
41
+
42
+ class ClientAuthManager:
43
+ """
44
+ Client Authentication Manager.
45
+
46
+ This class provides ONLY configuration validation for the embed-client.
47
+ All actual authentication execution is handled by mcp_proxy_adapter JsonRpcClient,
48
+ which uses mcp_security_framework internally.
49
+ """
50
+
51
+ def __init__(self, config: Dict[str, Any]):
52
+ """
53
+ Initialize authentication manager.
54
+
55
+ Args:
56
+ config: Authentication configuration dictionary
57
+
58
+ Note:
59
+ This class only validates configuration structure.
60
+ Actual authentication is handled by mcp_proxy_adapter JsonRpcClient.
61
+ """
62
+ self.config = config
63
+ self.logger = logging.getLogger(__name__)
64
+
65
+ def get_auth_headers(self, auth_method: str, **kwargs) -> Dict[str, str]:
66
+ """
67
+ Get authentication headers for requests.
68
+
69
+ Args:
70
+ auth_method: Authentication method
71
+ **kwargs: Additional authentication parameters
72
+
73
+ Returns:
74
+ Dictionary of headers
75
+ """
76
+ headers = {}
77
+
78
+ if auth_method == "api_key":
79
+ api_key = kwargs.get("api_key")
80
+ header_name = kwargs.get("header", "X-API-Key")
81
+ if api_key:
82
+ headers[header_name] = api_key
83
+
84
+ elif auth_method == "jwt":
85
+ token = kwargs.get("token")
86
+ if token:
87
+ headers["Authorization"] = f"Bearer {token}"
88
+
89
+ elif auth_method == "basic":
90
+ username = kwargs.get("username")
91
+ password = kwargs.get("password")
92
+ if username and password:
93
+ credentials = base64.b64encode(
94
+ f"{username}:{password}".encode()
95
+ ).decode()
96
+ headers["Authorization"] = f"Basic {credentials}"
97
+
98
+ elif auth_method == "certificate":
99
+ # Certificate authentication is handled at SSL level
100
+ pass
101
+
102
+ return headers
103
+
104
+ def validate_auth_config(self) -> List[str]:
105
+ """
106
+ Validate authentication configuration.
107
+
108
+ Returns:
109
+ List of validation errors
110
+ """
111
+ errors = []
112
+ auth_config = self.config.get("auth", {})
113
+ auth_method = auth_config.get("method", "none")
114
+
115
+ if auth_method == "api_key":
116
+ api_keys = auth_config.get("api_keys", {})
117
+ if not api_keys:
118
+ errors.append("API keys not configured for api_key authentication")
119
+
120
+ elif auth_method == "jwt":
121
+ jwt_config = auth_config.get("jwt", {})
122
+ if not jwt_config.get("secret"):
123
+ errors.append("JWT secret not configured")
124
+ if not jwt_config.get("username"):
125
+ errors.append("JWT username not configured")
126
+ if not jwt_config.get("password"):
127
+ errors.append("JWT password not configured")
128
+
129
+ elif auth_method == "certificate":
130
+ cert_config = auth_config.get("certificate", {})
131
+ if not cert_config.get("cert_file"):
132
+ errors.append("Certificate file not configured")
133
+ if not cert_config.get("key_file"):
134
+ errors.append("Key file not configured")
135
+
136
+ elif auth_method == "basic":
137
+ basic_config = auth_config.get("basic", {})
138
+ if not basic_config.get("username"):
139
+ errors.append("Basic auth username not configured")
140
+ if not basic_config.get("password"):
141
+ errors.append("Basic auth password not configured")
142
+
143
+ return errors
144
+
145
+ def is_auth_enabled(self) -> bool:
146
+ """Check if authentication is enabled."""
147
+ return self.config.get("auth", {}).get("method", "none") != "none"
148
+
149
+ def get_auth_method(self) -> str:
150
+ """Get current authentication method."""
151
+ return self.config.get("auth", {}).get("method", "none")
152
+
153
+ def get_supported_methods(self) -> List[str]:
154
+ """Get list of supported authentication methods."""
155
+ return ["api_key", "jwt", "certificate", "basic"]
156
+
157
+
158
+ def create_auth_manager(config: Dict[str, Any]) -> ClientAuthManager:
159
+ """
160
+ Create authentication manager from configuration.
161
+
162
+ Args:
163
+ config: Configuration dictionary
164
+
165
+ Returns:
166
+ ClientAuthManager instance
167
+
168
+ Note:
169
+ This manager only validates configuration structure.
170
+ Actual authentication is handled by mcp_proxy_adapter JsonRpcClient.
171
+ """
172
+ return ClientAuthManager(config)
173
+
174
+
175
+ def create_auth_headers(auth_method: str, **kwargs) -> Dict[str, str]:
176
+ """
177
+ Create authentication headers for requests.
178
+
179
+ Args:
180
+ auth_method: Authentication method
181
+ **kwargs: Authentication parameters
182
+
183
+ Returns:
184
+ Dictionary of headers
185
+ """
186
+ # Create temporary auth manager for header generation
187
+ temp_config = {"auth": {"method": auth_method}}
188
+ auth_manager = ClientAuthManager(temp_config)
189
+ return auth_manager.get_auth_headers(auth_method, **kwargs)
embed_client/cli.py ADDED
@@ -0,0 +1,483 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI Application for Text Vectorization
4
+ Command-line interface for embedding text using embed-client.
5
+
6
+ This CLI is fully based on the embed-client library and uses:
7
+ - ClientFactory for client creation
8
+ - ClientConfig for configuration management
9
+ - response_parsers for data extraction
10
+ - All authentication methods (API Key, JWT, Basic, Certificate)
11
+ - Queue management commands
12
+
13
+ Author: Vasiliy Zdanovskiy
14
+ email: vasilyvz@gmail.com
15
+ """
16
+
17
+ import argparse
18
+ import asyncio
19
+ import sys
20
+ from typing import Any, Dict # noqa: F401
21
+
22
+ from embed_client.config import ClientConfig
23
+ from embed_client.cli_core import VectorizationCLI, create_config_from_args
24
+ from embed_client.constants import DEFAULT_POLL_INTERVAL
25
+
26
+
27
+ async def main():
28
+ """Main CLI function."""
29
+ parser = argparse.ArgumentParser(
30
+ description="CLI Application for Text Vectorization (fully based on embed-client library)",
31
+ formatter_class=argparse.RawDescriptionHelpFormatter,
32
+ epilog="""
33
+ Examples:
34
+ # Vectorize text from command line
35
+ python -m embed_client vectorize "hello world" "test text"
36
+
37
+ # Vectorize text from file
38
+ python -m embed_client vectorize --file texts.txt
39
+
40
+ # Server-side embed_file: local JSON array file (upload + json_array), not line-based embed
41
+ python -m embed_client embed-file-json ./blocks.json
42
+
43
+ # Markdown → json_array (YAML front matter strip; body split on \\n\\n; per-block UTF-8 cap); then embed-file-json
44
+ python -m embed_client prepare-embed-file-json ./doc.md -o ./doc_embed_file.json
45
+ python -m embed_client embed-file-json ./doc_embed_file.json --show-progress
46
+
47
+ # Use HTTPS with API key authentication
48
+ python -m embed_client vectorize "hello world" --host https://localhost --port 8443 --api-key your-key
49
+
50
+ # Use mTLS with client certificates
51
+ python -m embed_client vectorize "hello world" --ssl --cert-file client.crt --key-file client.key
52
+
53
+ # Use JWT authentication
54
+ python -m embed_client vectorize "hello" --jwt-secret secret --jwt-username user --jwt-password pass
55
+
56
+ # Use config file
57
+ python -m embed_client --config config.json vectorize "hello"
58
+
59
+ # Queue management
60
+ python -m embed_client queue-list
61
+ python -m embed_client queue-status JOB_ID
62
+ python -m embed_client queue-cancel JOB_ID
63
+ python -m embed_client queue-wait JOB_ID
64
+
65
+ # Get help from service
66
+ python -m embed_client help
67
+
68
+ # Check service health
69
+ python -m embed_client health
70
+
71
+ # List available models
72
+ python -m embed_client models
73
+
74
+ # Get timing statistics (when server has timing_registration enabled)
75
+ python -m embed_client timing-stats
76
+ python -m embed_client timing-stats --file-path /path/to/timings.jsonl
77
+ """,
78
+ )
79
+
80
+ # Global options
81
+ parser.add_argument(
82
+ "--config", "-c", help="Path to configuration file (JSON or YAML)"
83
+ )
84
+ parser.add_argument(
85
+ "--host",
86
+ default="http://localhost",
87
+ help="Server host (default: http://localhost)",
88
+ )
89
+ parser.add_argument(
90
+ "--port", type=int, default=8001, help="Server port (default: 8001)"
91
+ )
92
+ parser.add_argument(
93
+ "--timeout", type=float, default=30.0, help="Request timeout in seconds"
94
+ )
95
+
96
+ # Authentication options
97
+ auth_group = parser.add_argument_group("authentication")
98
+ auth_group.add_argument("--api-key", help="API key for authentication")
99
+ auth_group.add_argument(
100
+ "--api-key-header", help="API key header name (default: X-API-Key)"
101
+ )
102
+ auth_group.add_argument("--jwt-secret", help="JWT secret for authentication")
103
+ auth_group.add_argument("--jwt-username", help="JWT username")
104
+ auth_group.add_argument("--jwt-password", help="JWT password")
105
+ auth_group.add_argument("--basic-username", help="Basic auth username")
106
+ auth_group.add_argument("--basic-password", help="Basic auth password")
107
+
108
+ # SSL/TLS options
109
+ ssl_group = parser.add_argument_group("ssl/tls")
110
+ ssl_group.add_argument("--ssl", action="store_true", help="Enable SSL/TLS")
111
+ ssl_group.add_argument("--cert-file", help="Client certificate file")
112
+ ssl_group.add_argument("--key-file", help="Client private key file")
113
+ ssl_group.add_argument("--ca-cert-file", help="CA certificate file")
114
+ ssl_group.add_argument(
115
+ "--insecure",
116
+ "--no-check-hostname",
117
+ dest="insecure",
118
+ action="store_true",
119
+ help=(
120
+ "Disable TLS certificate/hostname verification "
121
+ "(library check_hostname=False) for HTTPS and WebSocket (wss) "
122
+ "connections. Use for self-signed certificates. "
123
+ "Default: strict verification."
124
+ ),
125
+ )
126
+
127
+ # Commands
128
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
129
+
130
+ # Vectorize command
131
+ vectorize_parser = subparsers.add_parser("vectorize", help="Vectorize text")
132
+ vectorize_parser.add_argument("texts", nargs="*", help="Texts to vectorize")
133
+ vectorize_parser.add_argument(
134
+ "--file", "-f", help="File containing texts (one per line)"
135
+ )
136
+ vectorize_parser.add_argument(
137
+ "--format",
138
+ choices=["json", "csv", "vectors"],
139
+ default="json",
140
+ help="Output format (default: json)",
141
+ )
142
+ vectorize_parser.add_argument(
143
+ "--full-data",
144
+ action="store_true",
145
+ help="Show full embedding data (body, embedding, tokens, bm25_tokens)",
146
+ )
147
+ vectorize_parser.add_argument(
148
+ "--model",
149
+ help="Model name (server-side, optional)",
150
+ )
151
+ vectorize_parser.add_argument(
152
+ "--dimension",
153
+ type=int,
154
+ help="Expected vector dimension (server-side, optional)",
155
+ )
156
+ vectorize_parser.add_argument(
157
+ "--error-policy",
158
+ choices=["continue", "fail_fast"],
159
+ default="continue",
160
+ help="Error policy: continue (per-item errors) or fail_fast (default: continue)",
161
+ )
162
+ vectorize_parser.add_argument(
163
+ "--device",
164
+ choices=["auto", "cpu", "cuda", "cuda:0"],
165
+ help="Device for embedding (server-side: auto, cpu, cuda, cuda:0)",
166
+ )
167
+ vectorize_parser.add_argument(
168
+ "--wait-timeout",
169
+ type=float,
170
+ default=None,
171
+ metavar="SECS",
172
+ dest="wait_timeout",
173
+ help="Wait up to SECS seconds for job completion (default: no wait). Use 0 for unlimited.",
174
+ )
175
+
176
+ embed_file_json = subparsers.add_parser(
177
+ "embed-file-json",
178
+ help="Local JSON array file → adapter upload → embed_file (json_array)",
179
+ )
180
+ embed_file_json.add_argument(
181
+ "json_path",
182
+ help="Path to UTF-8 JSON file: non-empty array of strings (json_array)",
183
+ )
184
+ embed_file_json.add_argument(
185
+ "--max-block-bytes",
186
+ type=int,
187
+ default=None,
188
+ metavar="N",
189
+ help="Max UTF-8 bytes per block (default: env EMBED_CLIENT_MAX_JSON_BLOCK_BYTES or 1 MiB)",
190
+ )
191
+ embed_file_json.add_argument(
192
+ "--upload-filename",
193
+ default=None,
194
+ help="Optional filename hint for the transfer (default: basename of json_path)",
195
+ )
196
+ embed_file_json.add_argument(
197
+ "--wait-timeout",
198
+ type=float,
199
+ default=None,
200
+ metavar="SECS",
201
+ dest="embed_file_wait_timeout",
202
+ help="embed_file WebSocket wait timeout in seconds (omit for server default)",
203
+ )
204
+ embed_file_json.add_argument(
205
+ "--download-to",
206
+ default=None,
207
+ help="Write server result JSON to this path (omit for temp file)",
208
+ )
209
+ embed_file_json.add_argument(
210
+ "--skip-download",
211
+ action="store_true",
212
+ help="Skip HTTP download; still send transfer_ack when applicable",
213
+ )
214
+ embed_file_json.add_argument(
215
+ "--model",
216
+ help="Model name (server-side, optional)",
217
+ )
218
+ embed_file_json.add_argument(
219
+ "--dimension",
220
+ type=int,
221
+ help="Expected vector dimension (server-side, optional)",
222
+ )
223
+ embed_file_json.add_argument(
224
+ "--device",
225
+ choices=["auto", "cpu", "cuda", "cuda:0"],
226
+ help="Device for embedding (server-side)",
227
+ )
228
+ embed_file_json.add_argument(
229
+ "--error-policy",
230
+ choices=["continue", "fail_fast"],
231
+ default="continue",
232
+ help="Error policy for embed_file (default: continue)",
233
+ )
234
+ embed_file_json.add_argument(
235
+ "--show-progress",
236
+ action="store_true",
237
+ help="Print WebSocket/status JSON lines to stderr while waiting for embed_file",
238
+ )
239
+
240
+ prepare_embed = subparsers.add_parser(
241
+ "prepare-embed-file-json",
242
+ help="Markdown/text → UTF-8 json_array (split body on blank lines \\n\\n; per-block UTF-8 cap; no network)",
243
+ )
244
+ prepare_embed.add_argument(
245
+ "md_path",
246
+ help="Path to UTF-8 Markdown or plain text (.md) file",
247
+ )
248
+ prepare_embed.add_argument(
249
+ "-o",
250
+ "--output",
251
+ default=None,
252
+ metavar="PATH",
253
+ help="Output JSON path (default: <input_stem>_embed_file.json beside input)",
254
+ )
255
+ prepare_embed.add_argument(
256
+ "--max-block-bytes",
257
+ type=int,
258
+ default=None,
259
+ metavar="N",
260
+ dest="max_block_bytes",
261
+ help="Max UTF-8 bytes per paragraph block after \\n\\n split (default: env or 1 MiB; error if exceeded)",
262
+ )
263
+ prepare_embed.add_argument(
264
+ "--max-chunks",
265
+ type=int,
266
+ default=None,
267
+ metavar="N",
268
+ help="Emit at most N blocks (smoke tests on huge files; omit for full document)",
269
+ )
270
+ prepare_embed.add_argument(
271
+ "--no-strip-front-matter",
272
+ action="store_true",
273
+ help="Do not strip YAML front matter between first pair of --- lines",
274
+ )
275
+
276
+ # Health command
277
+ subparsers.add_parser("health", help="Check service health")
278
+
279
+ # Models command
280
+ subparsers.add_parser("models", help="List available embedding models")
281
+
282
+ # Timing stats command
283
+ timing_parser = subparsers.add_parser(
284
+ "timing-stats",
285
+ help="Get timing statistics (server must have timing_registration enabled)",
286
+ )
287
+ timing_parser.add_argument(
288
+ "--file-path",
289
+ help="Path to timing file on server (optional, uses server default if omitted)",
290
+ )
291
+
292
+ # Help command
293
+ help_parser = subparsers.add_parser("help", help="Get help from service")
294
+ help_parser.add_argument(
295
+ "--command",
296
+ dest="help_command",
297
+ help="Specific command to get help for",
298
+ )
299
+
300
+ # Commands command
301
+ subparsers.add_parser("commands", help="Get available commands")
302
+
303
+ # Queue commands
304
+ queue_list_parser = subparsers.add_parser("queue-list", help="List queued commands")
305
+ queue_list_parser.add_argument("--status", help="Filter by status")
306
+ queue_list_parser.add_argument("--limit", type=int, help="Limit number of results")
307
+
308
+ queue_status_parser = subparsers.add_parser("queue-status", help="Get job status")
309
+ queue_status_parser.add_argument("job_id", help="Job identifier")
310
+
311
+ queue_cancel_parser = subparsers.add_parser("queue-cancel", help="Cancel a job")
312
+ queue_cancel_parser.add_argument("job_id", help="Job identifier")
313
+
314
+ queue_wait_parser = subparsers.add_parser(
315
+ "queue-wait", help="Wait for job completion"
316
+ )
317
+ queue_wait_parser.add_argument("job_id", help="Job identifier")
318
+ queue_wait_parser.add_argument(
319
+ "--timeout", type=float, default=60.0, help="Timeout in seconds"
320
+ )
321
+ queue_wait_parser.add_argument(
322
+ "--poll-interval",
323
+ type=float,
324
+ default=DEFAULT_POLL_INTERVAL,
325
+ help=f"Poll interval in seconds (default: {DEFAULT_POLL_INTERVAL})",
326
+ )
327
+
328
+ queue_logs_parser = subparsers.add_parser("queue-logs", help="Get job logs")
329
+ queue_logs_parser.add_argument("job_id", help="Job identifier")
330
+
331
+ args = parser.parse_args()
332
+
333
+ if not args.command:
334
+ parser.print_help()
335
+ return 1
336
+
337
+ if args.command == "prepare-embed-file-json":
338
+ from embed_client.md_embed_file_prep import prepare_markdown_cli_main
339
+
340
+ return prepare_markdown_cli_main(args)
341
+
342
+ # Load configuration
343
+ config = None
344
+ config_dict = None
345
+
346
+ if args.config:
347
+ # Load from config file using ClientConfig
348
+ config = ClientConfig(args.config)
349
+ config.load_config()
350
+ if getattr(args, "insecure", False):
351
+ # --insecure overrides config file: disable TLS hostname/cert
352
+ # verification for HTTPS and WebSocket connections.
353
+ config.set("ssl.check_hostname", False)
354
+ else:
355
+ # Create config from command line arguments
356
+ config_dict = create_config_from_args(args)
357
+
358
+ # Create CLI instance
359
+ cli = VectorizationCLI()
360
+
361
+ try:
362
+ # Connect to service
363
+ if not await cli.connect(config=config, config_dict=config_dict):
364
+ return 1
365
+
366
+ # Execute command
367
+ if args.command == "vectorize":
368
+ texts = args.texts
369
+ if args.file:
370
+ # Use async file reading to avoid blocking the event loop
371
+ # Read file in executor to avoid blocking
372
+ loop = asyncio.get_event_loop()
373
+
374
+ def read_file_sync(file_path: str) -> list[str]:
375
+ """Read file synchronously in executor."""
376
+ with open(file_path, "r", encoding="utf-8") as f:
377
+ return [line.strip() for line in f if line.strip()]
378
+
379
+ file_texts = await loop.run_in_executor(None, read_file_sync, args.file)
380
+ texts.extend(file_texts)
381
+
382
+ if not texts:
383
+ print("āŒ No texts provided", file=sys.stderr)
384
+ return 1
385
+
386
+ embeddings = await cli.vectorize_texts(
387
+ texts,
388
+ args.format,
389
+ args.full_data,
390
+ timeout=getattr(args, "wait_timeout", None),
391
+ model=getattr(args, "model", None),
392
+ dimension=getattr(args, "dimension", None),
393
+ error_policy=getattr(args, "error_policy", "continue"),
394
+ device=getattr(args, "device", None),
395
+ )
396
+ if embeddings is None:
397
+ return 1
398
+
399
+ elif args.command == "embed-file-json":
400
+ ok = await cli.embed_file_local_json(
401
+ args.json_path,
402
+ max_block_bytes=getattr(args, "max_block_bytes", None),
403
+ upload_filename=getattr(args, "upload_filename", None),
404
+ wait_timeout=getattr(args, "embed_file_wait_timeout", None),
405
+ download_to=getattr(args, "download_to", None),
406
+ skip_download=bool(getattr(args, "skip_download", False)),
407
+ model=getattr(args, "model", None),
408
+ dimension=getattr(args, "dimension", None),
409
+ device=getattr(args, "device", None),
410
+ error_policy=getattr(args, "error_policy", "continue"),
411
+ show_progress=bool(getattr(args, "show_progress", False)),
412
+ )
413
+ if not ok:
414
+ return 1
415
+
416
+ elif args.command == "health":
417
+ if not await cli.health_check():
418
+ return 1
419
+
420
+ elif args.command == "help":
421
+ if not await cli.get_help(getattr(args, "help_command", None)):
422
+ return 1
423
+
424
+ elif args.command == "commands":
425
+ if not await cli.get_commands():
426
+ return 1
427
+
428
+ elif args.command == "models":
429
+ if not await cli.get_models():
430
+ return 1
431
+
432
+ elif args.command == "timing-stats":
433
+ if not await cli.get_timing_stats(getattr(args, "file_path", None)):
434
+ return 1
435
+
436
+ elif args.command == "queue-list":
437
+ if not await cli.queue_list(
438
+ getattr(args, "status", None), getattr(args, "limit", None)
439
+ ):
440
+ return 1
441
+
442
+ elif args.command == "queue-status":
443
+ if not await cli.queue_status(args.job_id):
444
+ return 1
445
+
446
+ elif args.command == "queue-cancel":
447
+ if not await cli.queue_cancel(args.job_id):
448
+ return 1
449
+
450
+ elif args.command == "queue-wait":
451
+ if not await cli.queue_wait(
452
+ args.job_id,
453
+ timeout=getattr(args, "timeout", 60.0),
454
+ poll_interval=getattr(args, "poll_interval", DEFAULT_POLL_INTERVAL),
455
+ ):
456
+ return 1
457
+
458
+ elif args.command == "queue-logs":
459
+ if not await cli.queue_logs(args.job_id):
460
+ return 1
461
+
462
+ return 0
463
+
464
+ except KeyboardInterrupt:
465
+ print("\nšŸ›‘ Interrupted by user", file=sys.stderr)
466
+ return 1
467
+ except Exception as e:
468
+ print(f"āŒ Error: {e}", file=sys.stderr)
469
+ import traceback
470
+
471
+ traceback.print_exc()
472
+ return 1
473
+ finally:
474
+ await cli.disconnect()
475
+
476
+
477
+ def cli_main():
478
+ """Synchronous entry point for CLI command."""
479
+ return asyncio.run(main())
480
+
481
+
482
+ if __name__ == "__main__":
483
+ sys.exit(cli_main())