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.
- manta/__init__.light.py +22 -0
- manta/__init__.py +83 -0
- manta/__main__.py +21 -0
- manta/apis/__init__.py +7 -0
- manta/apis/async_user_api.py +6458 -0
- manta/apis/graph.py +498 -0
- manta/apis/module.py +316 -0
- manta/apis/results.py +251 -0
- manta/apis/swarm.py +206 -0
- manta/apis/user_api.py +1016 -0
- manta/cli/__init__.py +1 -0
- manta/cli/commands/__init__.py +1 -0
- manta/cli/commands/base_handler.py +229 -0
- manta/cli/commands/doc.py +192 -0
- manta/cli/commands/install.py +346 -0
- manta/cli/commands/sdk.py +9 -0
- manta/cli/commands/sdk_cluster.py +211 -0
- manta/cli/commands/sdk_config.py +347 -0
- manta/cli/commands/sdk_globals.py +280 -0
- manta/cli/commands/sdk_logs.py +174 -0
- manta/cli/commands/sdk_main.py +167 -0
- manta/cli/commands/sdk_module.py +516 -0
- manta/cli/commands/sdk_nodes.py +168 -0
- manta/cli/commands/sdk_original.py +3873 -0
- manta/cli/commands/sdk_results.py +265 -0
- manta/cli/commands/sdk_swarm.py +454 -0
- manta/cli/commands/sdk_user.py +234 -0
- manta/cli/commands/status.py +292 -0
- manta/cli/component_detector.py +112 -0
- manta/cli/config_manager.py +445 -0
- manta/cli/main.py +265 -0
- manta/cli/utils/__init__.py +27 -0
- manta/cli/utils/converters.py +140 -0
- manta/clients/cluster_management_client.py +486 -0
- manta/clients/local_client.py +149 -0
- manta/clients/module_management_client.py +217 -0
- manta/clients/swarm_management_client.py +562 -0
- manta/clients/user_management_client.py +395 -0
- manta/clients/world_client.py +195 -0
- manta/light/__init__.py +31 -0
- manta/light/globals.py +245 -0
- manta/light/local.py +407 -0
- manta/light/logging_config.py +39 -0
- manta/light/path.py +116 -0
- manta/light/results.py +236 -0
- manta/light/task.py +100 -0
- manta/light/utils.py +217 -0
- manta/light/world.py +177 -0
- mantatech_sdk-0.5b0.dev65.dist-info/METADATA +1039 -0
- mantatech_sdk-0.5b0.dev65.dist-info/RECORD +54 -0
- mantatech_sdk-0.5b0.dev65.dist-info/WHEEL +5 -0
- mantatech_sdk-0.5b0.dev65.dist-info/entry_points.txt +2 -0
- mantatech_sdk-0.5b0.dev65.dist-info/licenses/LICENSE +683 -0
- 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())
|