mantatech-sdk 0.5b0.dev65__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 (54) hide show
  1. manta/__init__.light.py +22 -0
  2. manta/__init__.py +83 -0
  3. manta/__main__.py +21 -0
  4. manta/apis/__init__.py +7 -0
  5. manta/apis/async_user_api.py +6458 -0
  6. manta/apis/graph.py +498 -0
  7. manta/apis/module.py +316 -0
  8. manta/apis/results.py +251 -0
  9. manta/apis/swarm.py +206 -0
  10. manta/apis/user_api.py +1016 -0
  11. manta/cli/__init__.py +1 -0
  12. manta/cli/commands/__init__.py +1 -0
  13. manta/cli/commands/base_handler.py +229 -0
  14. manta/cli/commands/doc.py +192 -0
  15. manta/cli/commands/install.py +346 -0
  16. manta/cli/commands/sdk.py +9 -0
  17. manta/cli/commands/sdk_cluster.py +211 -0
  18. manta/cli/commands/sdk_config.py +347 -0
  19. manta/cli/commands/sdk_globals.py +280 -0
  20. manta/cli/commands/sdk_logs.py +174 -0
  21. manta/cli/commands/sdk_main.py +167 -0
  22. manta/cli/commands/sdk_module.py +516 -0
  23. manta/cli/commands/sdk_nodes.py +168 -0
  24. manta/cli/commands/sdk_original.py +3873 -0
  25. manta/cli/commands/sdk_results.py +265 -0
  26. manta/cli/commands/sdk_swarm.py +454 -0
  27. manta/cli/commands/sdk_user.py +234 -0
  28. manta/cli/commands/status.py +292 -0
  29. manta/cli/component_detector.py +112 -0
  30. manta/cli/config_manager.py +445 -0
  31. manta/cli/main.py +265 -0
  32. manta/cli/utils/__init__.py +27 -0
  33. manta/cli/utils/converters.py +140 -0
  34. manta/clients/cluster_management_client.py +486 -0
  35. manta/clients/local_client.py +149 -0
  36. manta/clients/module_management_client.py +217 -0
  37. manta/clients/swarm_management_client.py +562 -0
  38. manta/clients/user_management_client.py +395 -0
  39. manta/clients/world_client.py +195 -0
  40. manta/light/__init__.py +31 -0
  41. manta/light/globals.py +245 -0
  42. manta/light/local.py +407 -0
  43. manta/light/logging_config.py +39 -0
  44. manta/light/path.py +116 -0
  45. manta/light/results.py +236 -0
  46. manta/light/task.py +100 -0
  47. manta/light/utils.py +217 -0
  48. manta/light/world.py +177 -0
  49. mantatech_sdk-0.5b0.dev65.dist-info/METADATA +1039 -0
  50. mantatech_sdk-0.5b0.dev65.dist-info/RECORD +54 -0
  51. mantatech_sdk-0.5b0.dev65.dist-info/WHEEL +5 -0
  52. mantatech_sdk-0.5b0.dev65.dist-info/entry_points.txt +2 -0
  53. mantatech_sdk-0.5b0.dev65.dist-info/licenses/LICENSE +683 -0
  54. mantatech_sdk-0.5b0.dev65.dist-info/top_level.txt +1 -0
@@ -0,0 +1,445 @@
1
+ """Simplified SDK Configuration Manager for manta-sdk.
2
+
3
+ This module provides simple configuration management for SDK operations,
4
+ using individual config files in ~/.manta/sdk/ directory with an active config system.
5
+ Matches the pattern used by manta-node config management.
6
+ """
7
+
8
+ import threading
9
+ from dataclasses import asdict, dataclass, field
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ try:
15
+ import tomllib # Python 3.11+
16
+ except ImportError:
17
+ import tomli as tomllib # Python < 3.11 fallback
18
+
19
+ import toml # For writing TOML files
20
+
21
+ __all__ = ["SDKConfigManager", "SDKConfiguration"]
22
+
23
+
24
+ @dataclass
25
+ class SDKConfiguration:
26
+ """Simple SDK configuration matching AsyncUserAPI parameters."""
27
+
28
+ # Core AsyncUserAPI parameters (token is required)
29
+ token: str
30
+ host: str = "localhost"
31
+ port: int = 50052
32
+ cert_folder: Optional[str] = None
33
+
34
+ # Configuration metadata
35
+ name: str = "default"
36
+ description: str = "SDK configuration"
37
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat())
38
+ updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
39
+
40
+ def __post_init__(self):
41
+ """Validate configuration after initialization."""
42
+ if not self.token or self.token.strip() == "":
43
+ raise ValueError("token is required and cannot be empty")
44
+
45
+ if not self.host or self.host.strip() == "":
46
+ raise ValueError("host is required and cannot be empty")
47
+
48
+ if self.port <= 0 or self.port > 65535:
49
+ raise ValueError("port must be a valid port number (1-65535)")
50
+
51
+
52
+ class SDKConfigManager:
53
+ """Simple configuration manager for SDK matching node config pattern."""
54
+
55
+ DEFAULT_CONFIG_DIR = Path.home() / ".manta" / "sdk"
56
+ ACTIVE_CONFIG_FILE = DEFAULT_CONFIG_DIR / "active"
57
+
58
+ def __init__(self, config_dir: Optional[Path] = None):
59
+ """Initialize SDK configuration manager.
60
+
61
+ Args:
62
+ config_dir: Optional custom configuration directory
63
+ """
64
+ self.config_dir = Path(config_dir) if config_dir else self.DEFAULT_CONFIG_DIR
65
+ self.active_config_file = self.config_dir / "active"
66
+ self._lock = threading.RLock()
67
+
68
+ # Ensure config directory exists
69
+ self.config_dir.mkdir(parents=True, exist_ok=True)
70
+
71
+ def _ensure_config_dir(self):
72
+ """Ensure the configuration directory exists."""
73
+ self.config_dir.mkdir(parents=True, exist_ok=True)
74
+
75
+ def list_configs(self) -> List[str]:
76
+ """List all available configuration names.
77
+
78
+ Returns:
79
+ List of configuration names (without .toml extension)
80
+ """
81
+ self._ensure_config_dir()
82
+ configs = []
83
+
84
+ for config_file in self.config_dir.glob("*.toml"):
85
+ configs.append(config_file.stem)
86
+
87
+ return sorted(configs)
88
+
89
+ def config_exists(self, config_name: str) -> bool:
90
+ """Check if a configuration exists.
91
+
92
+ Args:
93
+ config_name: Name of the configuration
94
+
95
+ Returns:
96
+ True if configuration exists
97
+ """
98
+ config_path = self.config_dir / f"{config_name}.toml"
99
+ return config_path.exists()
100
+
101
+ def save_config(
102
+ self, config: SDKConfiguration, config_name: Optional[str] = None
103
+ ) -> bool:
104
+ """Save a configuration to file.
105
+
106
+ Args:
107
+ config: Configuration to save
108
+ config_name: Name for the configuration (uses config.name if None)
109
+
110
+ Returns:
111
+ True if saved successfully
112
+ """
113
+ if config_name is None:
114
+ config_name = config.name
115
+
116
+ config_path = self.config_dir / f"{config_name}.toml"
117
+
118
+ with self._lock:
119
+ try:
120
+ self._ensure_config_dir()
121
+
122
+ # Update timestamps
123
+ config.name = config_name
124
+ config.updated_at = datetime.now().isoformat()
125
+
126
+ # Convert to dictionary and save
127
+ config_dict = asdict(config)
128
+
129
+ with open(config_path, "w") as f:
130
+ toml.dump(config_dict, f)
131
+
132
+ return True
133
+
134
+ except Exception as e:
135
+ print(f"Error saving configuration: {e}")
136
+ return False
137
+
138
+ def load_config(self, config_name: str) -> Optional[SDKConfiguration]:
139
+ """Load a configuration from file.
140
+
141
+ Args:
142
+ config_name: Name of the configuration to load
143
+
144
+ Returns:
145
+ Configuration object or None if not found/invalid
146
+ """
147
+ config_path = self.config_dir / f"{config_name}.toml"
148
+
149
+ if not config_path.exists():
150
+ return None
151
+
152
+ try:
153
+ with open(config_path, "rb") as f:
154
+ data = tomllib.load(f)
155
+
156
+ # Create configuration object
157
+ return SDKConfiguration(**data)
158
+
159
+ except Exception as e:
160
+ print(f"Error loading configuration '{config_name}': {e}")
161
+ return None
162
+
163
+ def delete_config(self, config_name: str) -> bool:
164
+ """Delete a configuration.
165
+
166
+ Args:
167
+ config_name: Name of the configuration to delete
168
+
169
+ Returns:
170
+ True if deleted successfully
171
+ """
172
+ if config_name == "default":
173
+ print("Cannot delete the default configuration")
174
+ return False
175
+
176
+ config_path = self.config_dir / f"{config_name}.toml"
177
+
178
+ with self._lock:
179
+ try:
180
+ if config_path.exists():
181
+ config_path.unlink()
182
+
183
+ # If deleted config was active, switch to default
184
+ active_config = self.get_active_config_name()
185
+ if active_config == config_name:
186
+ self.set_active_config("default")
187
+
188
+ return True
189
+
190
+ except Exception as e:
191
+ print(f"Error deleting configuration: {e}")
192
+ return False
193
+
194
+ def get_active_config_name(self) -> str:
195
+ """Get the name of the active configuration.
196
+
197
+ Returns:
198
+ Name of active configuration (defaults to 'default')
199
+ """
200
+ if self.active_config_file.exists():
201
+ try:
202
+ return self.active_config_file.read_text().strip()
203
+ except Exception:
204
+ pass
205
+
206
+ return "default"
207
+
208
+ def set_active_config(self, config_name: str) -> bool:
209
+ """Set the active configuration.
210
+
211
+ Args:
212
+ config_name: Name of the configuration to make active
213
+
214
+ Returns:
215
+ True if set successfully
216
+ """
217
+ # Check if config exists
218
+ if not self.config_exists(config_name):
219
+ print(f"Configuration '{config_name}' not found")
220
+ return False
221
+
222
+ with self._lock:
223
+ try:
224
+ self._ensure_config_dir()
225
+ self.active_config_file.write_text(config_name)
226
+ return True
227
+
228
+ except Exception as e:
229
+ print(f"Error setting active configuration: {e}")
230
+ return False
231
+
232
+ def get_active_config(self) -> Optional[SDKConfiguration]:
233
+ """Get the active configuration.
234
+
235
+ Returns:
236
+ Active configuration or None if not found
237
+ """
238
+ active_name = self.get_active_config_name()
239
+ return self.load_config(active_name)
240
+
241
+ def create_default_config(self) -> bool:
242
+ """Create a default configuration if it doesn't exist.
243
+
244
+ Returns:
245
+ True if created successfully
246
+ """
247
+ if self.config_exists("default"):
248
+ return True
249
+
250
+ # Create minimal default config (user must provide token)
251
+ try:
252
+ default_config = SDKConfiguration(
253
+ name="default",
254
+ token="<REQUIRED_JWT_TOKEN>", # Placeholder that will trigger validation error
255
+ host="localhost",
256
+ port=50052,
257
+ description="Default SDK configuration - please set your JWT token",
258
+ )
259
+ except ValueError:
260
+ # Create with bypass for initial creation
261
+ default_config = SDKConfiguration.__new__(SDKConfiguration)
262
+ default_config.name = "default"
263
+ default_config.token = "<REQUIRED_JWT_TOKEN>"
264
+ default_config.host = "localhost"
265
+ default_config.port = 50052
266
+ default_config.cert_folder = None
267
+ default_config.description = (
268
+ "Default SDK configuration - please set your JWT token"
269
+ )
270
+ default_config.created_at = datetime.now().isoformat()
271
+ default_config.updated_at = datetime.now().isoformat()
272
+
273
+ success = self.save_config(default_config, "default")
274
+ if success:
275
+ self.set_active_config("default")
276
+
277
+ return success
278
+
279
+ def create_config(
280
+ self,
281
+ name: str,
282
+ token: str,
283
+ host: str = "localhost",
284
+ port: int = 50052,
285
+ cert_folder: Optional[str] = None,
286
+ description: Optional[str] = None,
287
+ ) -> bool:
288
+ """Create a new configuration.
289
+
290
+ Args:
291
+ name: Configuration name
292
+ token: JWT authentication token
293
+ host: Server host
294
+ port: Server port
295
+ cert_folder: Path to certificate folder
296
+ description: Configuration description
297
+
298
+ Returns:
299
+ True if configuration created successfully
300
+ """
301
+ if self.config_exists(name):
302
+ print(f"Configuration '{name}' already exists")
303
+ return False
304
+
305
+ try:
306
+ config = SDKConfiguration(
307
+ name=name,
308
+ token=token,
309
+ host=host,
310
+ port=port,
311
+ cert_folder=cert_folder,
312
+ description=description or f"SDK configuration '{name}'",
313
+ )
314
+
315
+ return self.save_config(config, name)
316
+
317
+ except ValueError as e:
318
+ print(f"Invalid configuration: {e}")
319
+ return False
320
+
321
+ def update_config(self, name: str, **kwargs) -> bool:
322
+ """Update an existing configuration.
323
+
324
+ Args:
325
+ name: Configuration name
326
+ **kwargs: Fields to update
327
+
328
+ Returns:
329
+ True if configuration updated successfully
330
+ """
331
+ config = self.load_config(name)
332
+ if not config:
333
+ print(f"Configuration '{name}' not found")
334
+ return False
335
+
336
+ # Update fields
337
+ if "token" in kwargs:
338
+ config.token = kwargs["token"]
339
+ if "host" in kwargs:
340
+ config.host = kwargs["host"]
341
+ if "port" in kwargs:
342
+ config.port = kwargs["port"]
343
+ if "cert_folder" in kwargs:
344
+ config.cert_folder = kwargs["cert_folder"]
345
+ if "description" in kwargs:
346
+ config.description = kwargs["description"]
347
+
348
+ config.updated_at = datetime.now().isoformat()
349
+
350
+ try:
351
+ # Validate updated configuration
352
+ config.__post_init__()
353
+ return self.save_config(config, name)
354
+ except ValueError as e:
355
+ print(f"Invalid configuration update: {e}")
356
+ return False
357
+
358
+ # delete_config method already implemented above
359
+
360
+ # set_active_config method already implemented above
361
+
362
+ # list_configs method already implemented above
363
+
364
+ def get_config(self, name: Optional[str] = None) -> Optional[SDKConfiguration]:
365
+ """Get a specific configuration or the active one.
366
+
367
+ Args:
368
+ name: Configuration name (uses active if None)
369
+
370
+ Returns:
371
+ Configuration object or None if not found
372
+ """
373
+ if name is None:
374
+ return self.get_active_config()
375
+ return self.load_config(name)
376
+
377
+ # get_active_config method already implemented above
378
+
379
+ def get_connection_params(
380
+ self, config_name: Optional[str] = None
381
+ ) -> Dict[str, Any]:
382
+ """Get connection parameters for AsyncUserAPI initialization.
383
+
384
+ Args:
385
+ config_name: Configuration name (uses active if None)
386
+
387
+ Returns:
388
+ Dictionary with connection parameters for AsyncUserAPI
389
+ """
390
+ config = self.get_config(config_name)
391
+ if not config:
392
+ return {}
393
+
394
+ # Return parameters matching AsyncUserAPI constructor
395
+ params = {
396
+ "token": config.token,
397
+ "host": config.host,
398
+ "port": config.port,
399
+ "cert_folder": config.cert_folder,
400
+ }
401
+
402
+ return params
403
+
404
+ def validate_config(self, config: SDKConfiguration) -> List[str]:
405
+ """Validate a configuration and return any errors.
406
+
407
+ Args:
408
+ config: Configuration to validate
409
+
410
+ Returns:
411
+ List of validation error messages (empty if valid)
412
+ """
413
+ errors = []
414
+
415
+ # Token validation
416
+ if not config.token or config.token.strip() == "":
417
+ errors.append("Token is required")
418
+ elif config.token.startswith("<") and config.token.endswith(">"):
419
+ errors.append(
420
+ "Please replace the placeholder token with your actual JWT token"
421
+ )
422
+ elif len(config.token) < 10:
423
+ errors.append("Token appears to be too short for a valid JWT token")
424
+
425
+ # Host validation
426
+ if not config.host or config.host.strip() == "":
427
+ errors.append("Host is required")
428
+
429
+ # Port validation
430
+ if config.port <= 0 or config.port > 65535:
431
+ errors.append("Port must be a valid port number (1-65535)")
432
+
433
+ # Certificate folder validation (if provided)
434
+ if config.cert_folder:
435
+ cert_path = Path(config.cert_folder).expanduser()
436
+ if not cert_path.exists():
437
+ errors.append(
438
+ f"Certificate folder does not exist: {config.cert_folder}"
439
+ )
440
+ elif not cert_path.is_dir():
441
+ errors.append(
442
+ f"Certificate folder is not a directory: {config.cert_folder}"
443
+ )
444
+
445
+ return errors
manta/cli/main.py ADDED
@@ -0,0 +1,265 @@
1
+ """Main unified Manta CLI entry point.
2
+
3
+ This module provides the main CLI interface that works across all Manta components
4
+ with dynamic component detection and installation assistance.
5
+ """
6
+
7
+ import argparse
8
+ import logging
9
+ import sys
10
+
11
+ from rich.console import Console
12
+
13
+ from .commands.doc import DocCommands
14
+ from .commands.install import InstallCommands
15
+ from .commands.status import StatusCommands
16
+ from .component_detector import ComponentDetector
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class MantaCLI:
22
+ """Main unified CLI application."""
23
+
24
+ def __init__(self):
25
+ self.console = Console()
26
+ self.detector = ComponentDetector()
27
+
28
+ # Base commands always available
29
+ self.install_cmd = InstallCommands(self.detector, self.console)
30
+ self.status_cmd = StatusCommands(self.detector, self.console)
31
+ self.doc_cmd = DocCommands(self.console)
32
+
33
+ # Component commands (loaded dynamically)
34
+ self.sdk_cmd = None
35
+ self.admin_main = None
36
+ self.node_main = None
37
+
38
+ # Try to load component commands
39
+ self._load_component_commands()
40
+
41
+ def _load_component_commands(self):
42
+ """Load component commands - SDK is always available, others detected."""
43
+ # SDK commands are always available since we're part of manta-sdk
44
+ try:
45
+ from manta.cli.commands.sdk import SDKCommands
46
+
47
+ self.sdk_cmd = SDKCommands(self.console)
48
+ except ImportError:
49
+ # This should never happen since we're in manta-sdk
50
+ self.sdk_cmd = None
51
+
52
+ # Try to get admin main function for direct delegation
53
+ try:
54
+ from manta_admin.cli.main import main as admin_main
55
+
56
+ self.admin_main = admin_main
57
+ except ImportError:
58
+ self.admin_main = None
59
+
60
+ # Try to get node main function for direct delegation
61
+ try:
62
+ from manta_node.cli.main import main as node_main
63
+
64
+ self.node_main = node_main
65
+ except ImportError:
66
+ self.node_main = None
67
+
68
+ def print(self, *args, **kwargs):
69
+ """Rich print function."""
70
+ self.console.print(*args, **kwargs)
71
+
72
+ def print_error(self, message: str):
73
+ """Print error message."""
74
+ self.console.print(f"[red]Error:[/red] {message}")
75
+
76
+ def print_success(self, message: str):
77
+ """Print success message."""
78
+ self.console.print(f"[green]Success:[/green] {message}")
79
+
80
+ def print_warning(self, message: str):
81
+ """Print warning message."""
82
+ self.console.print(f"[yellow]Warning:[/yellow] {message}")
83
+
84
+ def handle_missing_component(self, component: str, command: str):
85
+ """Handle missing component error with installation guidance."""
86
+ comp_info = self.detector.get_component_info(component)
87
+
88
+ if comp_info:
89
+ self.print_error(
90
+ f"Command '{command}' requires {comp_info.name} but it's not installed."
91
+ )
92
+ self.print(f"\nTo install {comp_info.name}:")
93
+
94
+ # Suggest installation command
95
+ install_cmd = self.detector.get_installation_command(component)
96
+ self.print(f" {install_cmd}")
97
+
98
+ self.print("\nAlternatively, use the installation wizard:")
99
+ self.print(" manta install wizard")
100
+ else:
101
+ self.print_error(f"Unknown component: {component}")
102
+
103
+
104
+ def create_parser(cli: MantaCLI) -> argparse.ArgumentParser:
105
+ """Create main argument parser with dynamic subcommands."""
106
+ parser = argparse.ArgumentParser(
107
+ prog="manta",
108
+ description="Unified Manta CLI - Manage SDK, Node, and Admin operations",
109
+ formatter_class=argparse.RawDescriptionHelpFormatter,
110
+ epilog="""
111
+ Examples:
112
+ manta status # Show component status
113
+ manta install wizard # Interactive installation
114
+ manta doc # View documentation
115
+
116
+ Component Commands (when installed):
117
+ manta sdk <command> # SDK operations (user, cluster, swarm, results)
118
+ manta node <command> # Node operations (start, stop, config, logs)
119
+ """,
120
+ )
121
+
122
+ parser.add_argument("--version", action="version", version="%(prog)s 0.5b0")
123
+
124
+ parser.add_argument("--debug", action="store_true", help="Enable debug output")
125
+
126
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
127
+
128
+ # Status command (always available)
129
+ status_parser = subparsers.add_parser("status", help="Show Manta platform status")
130
+ status_parser.add_argument(
131
+ "status_command",
132
+ nargs="?",
133
+ choices=["installation", "system", "services"],
134
+ help="Specific status to show",
135
+ )
136
+
137
+ # Install command (always available)
138
+ install_parser = subparsers.add_parser("install", help="Install Manta components")
139
+ install_parser.add_argument(
140
+ "install_command",
141
+ nargs="?",
142
+ choices=["wizard", "check", "sdk", "admin", "node", "all"],
143
+ help="Installation command",
144
+ )
145
+
146
+ # Documentation command (always available)
147
+ doc_parser = subparsers.add_parser("doc", help="View Manta documentation")
148
+ doc_parser.add_argument(
149
+ "doc_topic",
150
+ nargs="?",
151
+ choices=[
152
+ "main",
153
+ "sdk",
154
+ "admin",
155
+ "node",
156
+ "api",
157
+ "examples",
158
+ "quickstart",
159
+ "architecture",
160
+ "configuration",
161
+ "deployment",
162
+ ],
163
+ help="Documentation topic to view",
164
+ )
165
+ doc_parser.add_argument(
166
+ "--open", action="store_true", help="Open documentation in browser"
167
+ )
168
+
169
+ # SDK commands (always available since we're part of manta-sdk)
170
+ if cli.sdk_cmd:
171
+ sdk_parser = subparsers.add_parser(
172
+ "sdk", help="SDK operations (user, swarm, module, cluster, results, config)"
173
+ )
174
+ sdk_subparsers = sdk_parser.add_subparsers(
175
+ dest="sdk_command", help="SDK commands"
176
+ )
177
+
178
+ # Add SDK subcommands (including config)
179
+ cli.sdk_cmd.add_subparsers(sdk_subparsers)
180
+
181
+ # Node commands (if installed) - simplified delegation
182
+ if cli.node_main:
183
+ node_parser = subparsers.add_parser(
184
+ "node", help="Node operations (start, stop, status, cluster)"
185
+ )
186
+ node_parser.add_argument(
187
+ "node_args",
188
+ nargs=argparse.REMAINDER,
189
+ help="Node command arguments (will be passed to manta-node CLI)",
190
+ )
191
+
192
+ return parser
193
+
194
+
195
+ def main():
196
+ """Main entry point for the Manta CLI."""
197
+ # Create CLI instance
198
+ cli = MantaCLI()
199
+
200
+ # Create parser
201
+ parser = create_parser(cli)
202
+
203
+ # Parse arguments
204
+ args = parser.parse_args()
205
+
206
+ # Configure logging
207
+ if args.debug:
208
+ logging.basicConfig(level=logging.DEBUG)
209
+ else:
210
+ logging.basicConfig(level=logging.WARNING)
211
+
212
+ # Handle commands
213
+ try:
214
+ if not args.command:
215
+ parser.print_help()
216
+ return 0
217
+
218
+ # Base commands (always available)
219
+ if args.command == "status":
220
+ return cli.status_cmd.handle(args)
221
+ elif args.command == "install":
222
+ return cli.install_cmd.handle(args)
223
+ elif args.command == "doc":
224
+ return cli.doc_cmd.handle(args)
225
+
226
+ # Component commands (check if installed)
227
+ elif args.command == "sdk":
228
+ if cli.sdk_cmd:
229
+ return cli.sdk_cmd.handle(args)
230
+ else:
231
+ cli.handle_missing_component("sdk", "manta sdk")
232
+ return 1
233
+
234
+ elif args.command == "node":
235
+ if cli.node_main:
236
+ # Direct delegation to manta-node CLI
237
+ node_args = getattr(args, "node_args", [])
238
+ try:
239
+ return cli.node_main(node_args)
240
+ except Exception as e:
241
+ cli.print_error(f"Error executing node command: {e}")
242
+ return 1
243
+ else:
244
+ cli.handle_missing_component("node", "manta node")
245
+ return 1
246
+
247
+ else:
248
+ cli.print_error(f"Unknown command: {args.command}")
249
+ parser.print_help()
250
+ return 1
251
+
252
+ except KeyboardInterrupt:
253
+ cli.print("\n\nInterrupted by user")
254
+ return 130
255
+ except Exception as e:
256
+ cli.print_error(f"An error occurred: {e}")
257
+ if args.debug:
258
+ import traceback
259
+
260
+ traceback.print_exc()
261
+ return 1
262
+
263
+
264
+ if __name__ == "__main__":
265
+ sys.exit(main())