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,454 @@
1
+ """SDK swarm management and simulation commands."""
2
+
3
+ import signal
4
+ import time
5
+ from typing import Optional
6
+
7
+ from rich.panel import Panel
8
+ from rich.prompt import Confirm
9
+ from rich.table import Table
10
+
11
+ from .base_handler import BaseSDKHandler
12
+ from ..utils.converters import (
13
+ enum_to_string,
14
+ timestamp_to_string,
15
+ SWARM_STATUS_MAP,
16
+ TASK_STATUS_MAP,
17
+ )
18
+
19
+
20
+ class SDKSwarmHandler(BaseSDKHandler):
21
+ """Handle swarm-related SDK commands including simulations."""
22
+
23
+ def add_subparsers(self, parent_parser):
24
+ """Add swarm command subparsers."""
25
+ self.parser = parent_parser # Store parser reference
26
+ subparsers = parent_parser.add_subparsers(
27
+ dest="swarm_command", help="Swarm commands"
28
+ )
29
+
30
+ # List swarms command
31
+ list_parser = subparsers.add_parser("list", help="List user swarms")
32
+ list_parser.add_argument("--cluster-id", help="Filter by cluster ID")
33
+
34
+ # List swarm IDs command
35
+ list_ids_parser = subparsers.add_parser("list-ids", help="List swarm IDs only")
36
+
37
+ # Show swarm command
38
+ show_parser = subparsers.add_parser(
39
+ "show", help="Show detailed swarm information"
40
+ )
41
+ show_parser.add_argument("swarm_id", help="Swarm ID to show")
42
+
43
+ # Show swarm tasks command
44
+ tasks_parser = subparsers.add_parser("tasks", help="Show swarm tasks")
45
+ tasks_parser.add_argument("swarm_id", help="Swarm ID to show tasks for")
46
+
47
+ # Start swarm command
48
+ start_parser = subparsers.add_parser("start", help="Start a swarm")
49
+ start_parser.add_argument("swarm_id", help="Swarm ID to start")
50
+
51
+ # Stop swarm command
52
+ stop_parser = subparsers.add_parser("stop", help="Stop a swarm")
53
+ stop_parser.add_argument("swarm_id", help="Swarm ID to stop")
54
+ stop_parser.add_argument(
55
+ "--force", action="store_true", help="Force stop without confirmation"
56
+ )
57
+
58
+ # Delete/Remove swarm command
59
+ delete_parser = subparsers.add_parser("remove", help="Delete a swarm")
60
+ delete_parser.add_argument("swarm_id", help="Swarm ID to delete")
61
+ delete_parser.add_argument(
62
+ "--force", action="store_true", help="Skip confirmation"
63
+ )
64
+
65
+ # Monitor swarm command
66
+ monitor_parser = subparsers.add_parser(
67
+ "monitor", help="Monitor swarm execution"
68
+ )
69
+ monitor_parser.add_argument("swarm_id", help="Swarm ID to monitor")
70
+
71
+ # Add profile override to all subcommands
72
+ for parser in [
73
+ list_parser,
74
+ list_ids_parser,
75
+ show_parser,
76
+ tasks_parser,
77
+ start_parser,
78
+ stop_parser,
79
+ delete_parser,
80
+ monitor_parser,
81
+ ]:
82
+ parser.add_argument("--profile", help="Use specific profile")
83
+
84
+ def handle(self, args) -> int:
85
+ """Handle swarm commands."""
86
+ # Handle both swarm_command and simulation_command (for backwards compatibility)
87
+ command = getattr(args, "swarm_command", None) or getattr(
88
+ args, "simulation_command", None
89
+ )
90
+
91
+ if not command:
92
+ if hasattr(self, "parser"):
93
+ self.parser.print_help()
94
+ return 0 # Return 0 for help display
95
+
96
+ if command == "list":
97
+ return self.list_swarms(getattr(args, "cluster_id", None))
98
+ elif command == "list-ids":
99
+ return self.list_swarm_ids()
100
+ elif command == "show":
101
+ return self.show_swarm(args.swarm_id)
102
+ elif command == "tasks":
103
+ return self.show_swarm_tasks(args.swarm_id)
104
+ elif command == "start":
105
+ return self.start_swarm(args.swarm_id)
106
+ elif command == "stop":
107
+ return self.stop_swarm(args.swarm_id, getattr(args, "force", False))
108
+ elif command in ["delete", "remove"]:
109
+ return self.delete_swarm(args.swarm_id, getattr(args, "force", False))
110
+ elif command == "monitor":
111
+ return self.monitor_swarm(args.swarm_id)
112
+ else:
113
+ self.print_error(f"Unknown swarm command: {command}")
114
+ return 1
115
+
116
+ def list_swarm_ids(self) -> int:
117
+ """List swarm IDs only."""
118
+ api = self._get_user_api()
119
+ if not api:
120
+ return 1
121
+
122
+ try:
123
+
124
+ async def run():
125
+ return await api.list_swarm_ids()
126
+
127
+ swarm_ids = self._run_with_progress(run, "Fetching swarm IDs...")
128
+
129
+ if swarm_ids is None:
130
+ return 1
131
+
132
+ if not swarm_ids:
133
+ self.print("No swarms found.")
134
+ return 0
135
+
136
+ self.print(f"Found {len(swarm_ids)} swarms:")
137
+ for swarm_id in swarm_ids:
138
+ self.print(f" {swarm_id}")
139
+
140
+ return 0
141
+
142
+ except Exception as e:
143
+ self.print_error(f"Failed to list swarm IDs: {e}")
144
+ return 1
145
+
146
+ def list_swarms(self, cluster_id: Optional[str] = None) -> int:
147
+ """List user swarms, optionally filtered by cluster."""
148
+ api = self._get_user_api()
149
+ if not api:
150
+ return 1
151
+
152
+ try:
153
+
154
+ async def run():
155
+ # stream_and_fetch_swarms doesn't have cluster_id parameter
156
+ # Get all swarms and filter by cluster if needed
157
+ swarms = await api.stream_and_fetch_swarms()
158
+ if cluster_id:
159
+ # Filter swarms by cluster_id
160
+ swarms = [s for s in swarms if s.get("cluster_id") == cluster_id]
161
+ return swarms
162
+
163
+ message = f"Fetching swarms{f' for cluster {cluster_id}' if cluster_id else ''}..."
164
+ swarms = self._run_with_progress(run, message)
165
+
166
+ if swarms is None:
167
+ return 1
168
+
169
+ # Display swarms
170
+ if not swarms:
171
+ self.print("No swarms found.")
172
+ return 0
173
+
174
+ table = Table(
175
+ title=f"User Swarms{f' (Cluster: {cluster_id})' if cluster_id else ''}"
176
+ )
177
+ table.add_column("Swarm ID", style="cyan")
178
+ table.add_column("Name", style="blue")
179
+ table.add_column("Status", style="green")
180
+ table.add_column("Cluster", style="magenta")
181
+ table.add_column("Created", style="yellow")
182
+
183
+ for swarm in swarms:
184
+ # Convert status enum to string
185
+ status = swarm.get("status")
186
+ status_str = (
187
+ enum_to_string(status, SWARM_STATUS_MAP)
188
+ if status is not None
189
+ else "Unknown"
190
+ )
191
+
192
+ # Convert timestamp
193
+ created_at = timestamp_to_string(
194
+ swarm.get("created_at") or swarm.get("creation_date")
195
+ )
196
+
197
+ table.add_row(
198
+ swarm.get("swarm_id", "Unknown"),
199
+ swarm.get("name", "Unknown"),
200
+ status_str,
201
+ swarm.get("cluster_id", "Unknown"),
202
+ created_at,
203
+ )
204
+
205
+ self.console.print(table)
206
+
207
+ return 0
208
+
209
+ except Exception as e:
210
+ self.print_error(f"Failed to list swarms: {e}")
211
+ return 1
212
+
213
+ def show_swarm(self, swarm_id: str) -> int:
214
+ """Show detailed swarm information."""
215
+ api = self._get_user_api()
216
+ if not api:
217
+ return 1
218
+
219
+ try:
220
+
221
+ async def run():
222
+ return await api.get_swarm(swarm_id)
223
+
224
+ swarm = self._run_with_progress(run, "Fetching swarm details...")
225
+
226
+ if swarm is None:
227
+ return 1
228
+
229
+ # Display swarm details
230
+ status = swarm.get("status")
231
+ status_str = (
232
+ enum_to_string(status, SWARM_STATUS_MAP)
233
+ if status is not None
234
+ else "Unknown"
235
+ )
236
+ created_at = timestamp_to_string(
237
+ swarm.get("creation_date") or swarm.get("created_at")
238
+ )
239
+
240
+ panel = Panel.fit(
241
+ f"[cyan]Swarm ID:[/cyan] {swarm.get('swarm_id', 'Unknown')}\n"
242
+ f"[cyan]Name:[/cyan] {swarm.get('name', 'Unknown')}\n"
243
+ f"[cyan]Status:[/cyan] {status_str}\n"
244
+ f"[cyan]Cluster ID:[/cyan] {swarm.get('cluster_id', 'Unknown')}\n"
245
+ f"[cyan]Task Count:[/cyan] {swarm.get('task_count', 0)}\n"
246
+ f"[cyan]Node Count:[/cyan] {swarm.get('node_count', 0)}\n"
247
+ f"[cyan]Iteration:[/cyan] {swarm.get('iteration', 0)}\n"
248
+ f"[cyan]Created:[/cyan] {created_at}",
249
+ title=f"Swarm: {swarm_id}",
250
+ )
251
+ self.console.print(panel)
252
+
253
+ return 0
254
+
255
+ except Exception as e:
256
+ self.print_error(f"Failed to get swarm details: {e}")
257
+ return 1
258
+
259
+ def show_swarm_tasks(self, swarm_id: str) -> int:
260
+ """Show tasks for a swarm."""
261
+ api = self._get_user_api()
262
+ if not api:
263
+ return 1
264
+
265
+ try:
266
+
267
+ async def run():
268
+ # Use stream_swarm_tasks and collect all tasks
269
+ tasks = []
270
+ async for task in api.stream_swarm_tasks(swarm_id):
271
+ tasks.append(task)
272
+ return tasks
273
+
274
+ tasks = self._run_with_progress(
275
+ run, f"Fetching tasks for swarm {swarm_id}..."
276
+ )
277
+
278
+ if tasks is None:
279
+ return 1
280
+
281
+ if not tasks:
282
+ self.print("No tasks found for this swarm.")
283
+ return 0
284
+
285
+ table = Table(title=f"Tasks for Swarm: {swarm_id}")
286
+ table.add_column("Task ID", style="cyan")
287
+ table.add_column("Status", style="green")
288
+ table.add_column("Node", style="blue")
289
+ table.add_column("Started", style="yellow")
290
+
291
+ for task in tasks:
292
+ # Convert status enum to string
293
+ status = task.get("status")
294
+ status_str = (
295
+ enum_to_string(status, TASK_STATUS_MAP)
296
+ if status is not None
297
+ else "Unknown"
298
+ )
299
+
300
+ # Convert timestamp
301
+ started_at = timestamp_to_string(
302
+ task.get("started_at") or task.get("start_time")
303
+ )
304
+
305
+ table.add_row(
306
+ task.get("task_id", "Unknown"),
307
+ status_str,
308
+ task.get("node_id", "Unknown"),
309
+ started_at,
310
+ )
311
+
312
+ self.console.print(table)
313
+
314
+ return 0
315
+
316
+ except Exception as e:
317
+ self.print_error(f"Failed to get swarm tasks: {e}")
318
+ return 1
319
+
320
+ def start_swarm(self, swarm_id: str) -> int:
321
+ """Start a swarm."""
322
+ api = self._get_user_api()
323
+ if not api:
324
+ return 1
325
+
326
+ try:
327
+
328
+ async def run():
329
+ return await api.start_swarm(swarm_id)
330
+
331
+ result = self._run_with_progress(run, f"Starting swarm {swarm_id}...")
332
+
333
+ if result is None:
334
+ return 1
335
+
336
+ self.print_success("Swarm started successfully")
337
+ return 0
338
+
339
+ except Exception as e:
340
+ self.print_error(f"Failed to start swarm: {e}")
341
+ return 1
342
+
343
+ def stop_swarm(self, swarm_id: str, force: bool = False) -> int:
344
+ """Stop a swarm."""
345
+ if not force:
346
+ confirm = Confirm.ask(f"Are you sure you want to stop swarm '{swarm_id}'?")
347
+ if not confirm:
348
+ self.print("Operation cancelled.")
349
+ return 0
350
+
351
+ api = self._get_user_api()
352
+ if not api:
353
+ return 1
354
+
355
+ try:
356
+
357
+ async def run():
358
+ return await api.stop_swarm(swarm_id)
359
+
360
+ result = self._run_with_progress(run, "Stopping swarm...")
361
+
362
+ if result is None:
363
+ return 1
364
+
365
+ self.print_success("Swarm stopped successfully")
366
+ return 0
367
+
368
+ except Exception as e:
369
+ self.print_error(f"Failed to stop swarm: {e}")
370
+ return 1
371
+
372
+ def delete_swarm(self, swarm_id: str, force: bool = False) -> int:
373
+ """Delete a swarm."""
374
+ if not force:
375
+ confirm = Confirm.ask(
376
+ f"Are you sure you want to delete swarm '{swarm_id}'?"
377
+ )
378
+ if not confirm:
379
+ self.print("Operation cancelled.")
380
+ return 0
381
+
382
+ api = self._get_user_api()
383
+ if not api:
384
+ return 1
385
+
386
+ try:
387
+
388
+ async def run():
389
+ return await api.remove_swarm(swarm_id)
390
+
391
+ result = self._run_with_progress(run, "Deleting swarm...")
392
+
393
+ if result is None:
394
+ return 1
395
+
396
+ self.print_success("Swarm deleted successfully")
397
+ return 0
398
+
399
+ except Exception as e:
400
+ self.print_error(f"Failed to delete swarm: {e}")
401
+ return 1
402
+
403
+ def monitor_swarm(self, swarm_id: str) -> int:
404
+ """Monitor swarm execution in real-time."""
405
+ api = self._get_user_api()
406
+ if not api:
407
+ return 1
408
+
409
+ self.print(f"Monitoring swarm: {swarm_id}")
410
+ self.print("Press Ctrl+C to stop monitoring...")
411
+
412
+ try:
413
+ # Set up signal handler for graceful shutdown
414
+ shutdown_flag = False
415
+
416
+ def signal_handler(sig, frame):
417
+ nonlocal shutdown_flag
418
+ shutdown_flag = True
419
+
420
+ signal.signal(signal.SIGINT, signal_handler)
421
+
422
+ while not shutdown_flag:
423
+ try:
424
+
425
+ async def get_status():
426
+ return await api.get_swarm(swarm_id)
427
+
428
+ swarm_status = self._run_async(get_status())
429
+
430
+ # Display current status
431
+ status = swarm_status.get("status")
432
+ status_str = (
433
+ enum_to_string(status, SWARM_STATUS_MAP)
434
+ if status is not None
435
+ else "Unknown"
436
+ )
437
+ status_text = f"Status: {status_str}"
438
+ self.console.print(f"[{time.strftime('%H:%M:%S')}] {status_text}")
439
+
440
+ # Check if swarm is finished
441
+ status = swarm_status.get("status", "").lower()
442
+ if status in ["completed", "failed", "cancelled"]:
443
+ self.print(f"Swarm execution {status}.")
444
+ return 0
445
+
446
+ time.sleep(5) # Poll every 5 seconds
447
+
448
+ except KeyboardInterrupt:
449
+ self.print("\nMonitoring stopped.")
450
+ return 130 # KeyboardInterrupt exit code
451
+
452
+ except Exception as e:
453
+ self.print_error(f"Monitoring failed: {e}")
454
+ return 1
@@ -0,0 +1,234 @@
1
+ """SDK user and authentication commands."""
2
+
3
+ import asyncio
4
+
5
+ from rich.panel import Panel
6
+ from rich.progress import Progress, SpinnerColumn, TextColumn
7
+ from rich.table import Table
8
+
9
+ from .base_handler import BaseSDKHandler
10
+
11
+
12
+ class SDKUserHandler(BaseSDKHandler):
13
+ """Handle user-related SDK commands."""
14
+
15
+ def add_subparsers(self, parent_parser):
16
+ """Add user command subparsers."""
17
+ subparsers = parent_parser.add_subparsers(
18
+ dest="user_command", help="User commands"
19
+ )
20
+
21
+ # User status command
22
+ status_parser = subparsers.add_parser(
23
+ "status", help="Check user service status"
24
+ )
25
+
26
+ # Get user info command
27
+ get_parser = subparsers.add_parser(
28
+ "get-user", help="Get current user information"
29
+ )
30
+
31
+ # List clusters command
32
+ list_clusters_parser = subparsers.add_parser(
33
+ "list-clusters", help="List available clusters"
34
+ )
35
+
36
+ # List swarms command
37
+ list_swarms_parser = subparsers.add_parser(
38
+ "list-swarms", help="List user swarms"
39
+ )
40
+ list_swarms_parser.add_argument("--cluster-id", help="Filter by cluster ID")
41
+
42
+ # Add profile override to all subcommands
43
+ for parser in [
44
+ status_parser,
45
+ get_parser,
46
+ list_clusters_parser,
47
+ list_swarms_parser,
48
+ ]:
49
+ parser.add_argument("--profile", help="Use specific profile")
50
+
51
+ def handle(self, args) -> int:
52
+ """Handle user commands."""
53
+ if not args.user_command:
54
+ self.print_error("User command required")
55
+ return 1
56
+
57
+ if args.user_command == "status":
58
+ return self.check_user_service_status()
59
+ elif args.user_command == "get-user":
60
+ return self.get_user_info()
61
+ elif args.user_command == "list-clusters":
62
+ return self.list_clusters()
63
+ elif args.user_command == "list-swarms":
64
+ return self.list_swarms(getattr(args, "cluster_id", None))
65
+ else:
66
+ self.print_error(f"Unknown user command: {args.user_command}")
67
+ return 1
68
+
69
+ def check_user_service_status(self) -> int:
70
+ """Check user service status."""
71
+ api = self._get_user_api()
72
+ if not api:
73
+ return 1
74
+
75
+ try:
76
+
77
+ async def run():
78
+ # Try to get user info to test connection
79
+ user_info = await api.get_user()
80
+ return user_info is not None
81
+
82
+ status = self._run_with_progress(run, "Checking user service status...")
83
+
84
+ if status:
85
+ self.print_success("User service is accessible")
86
+ return 0
87
+ else:
88
+ self.print_error("User service is not accessible")
89
+ return 1
90
+
91
+ except Exception as e:
92
+ self.print_error(f"User service check failed: {e}")
93
+ return 1
94
+
95
+ def get_user_info(self) -> int:
96
+ """Get current user information."""
97
+ api = self._get_user_api()
98
+ if not api:
99
+ return 1
100
+
101
+ try:
102
+
103
+ async def run():
104
+ user_info = await api.get_user()
105
+ return user_info
106
+
107
+ with Progress(
108
+ SpinnerColumn(),
109
+ TextColumn("[progress.description]{task.description}"),
110
+ console=self.console,
111
+ ) as progress:
112
+ task = progress.add_task("Getting user information...", total=None)
113
+ user_info = asyncio.run(run())
114
+ progress.update(task, completed=True)
115
+
116
+ # Display user info
117
+ panel = Panel.fit(
118
+ f"[cyan]User ID:[/cyan] {user_info.get('user_id', 'Unknown')}\n"
119
+ f"[cyan]Username:[/cyan] {user_info.get('username', 'Unknown')}\n"
120
+ f"[cyan]Email:[/cyan] {user_info.get('email', 'Unknown')}\n"
121
+ f"[cyan]Roles:[/cyan] {', '.join(user_info.get('roles', []))}",
122
+ title="User Information",
123
+ )
124
+ self.console.print(panel)
125
+ return 0
126
+
127
+ except Exception as e:
128
+ self.print_error(f"Failed to get user information: {e}")
129
+ return 1
130
+
131
+ def list_clusters(self) -> int:
132
+ """List available clusters."""
133
+ api = self._get_user_api()
134
+ if not api:
135
+ return 1
136
+
137
+ try:
138
+
139
+ async def run():
140
+ clusters = await api.stream_and_fetch_clusters()
141
+ return clusters
142
+
143
+ with Progress(
144
+ SpinnerColumn(),
145
+ TextColumn("[progress.description]{task.description}"),
146
+ console=self.console,
147
+ ) as progress:
148
+ task = progress.add_task("Fetching clusters...", total=None)
149
+ clusters = asyncio.run(run())
150
+ progress.update(task, completed=True)
151
+
152
+ # Display clusters
153
+ if not clusters:
154
+ self.print("No clusters found.")
155
+ return 0
156
+
157
+ table = Table(title="Available Clusters")
158
+ table.add_column("Cluster ID", style="cyan")
159
+ table.add_column("Name", style="blue")
160
+ table.add_column("Status", style="green")
161
+ table.add_column("Nodes", style="magenta")
162
+
163
+ for cluster in clusters:
164
+ # UserAPI always returns dictionaries
165
+ cluster_id = cluster.get("cluster_id", "Unknown")
166
+ name = cluster.get("name", "Unknown")
167
+ status = cluster.get("status", "Unknown")
168
+ node_count = str(cluster.get("node_count", 0))
169
+
170
+ table.add_row(cluster_id, name, status, node_count)
171
+
172
+ self.console.print(table)
173
+
174
+ return 0
175
+
176
+ except Exception as e:
177
+ self.print_error(f"Failed to list clusters: {e}")
178
+ return 1
179
+
180
+ def list_swarms(self, cluster_id: str = None) -> int:
181
+ """List user swarms, optionally filtered by cluster."""
182
+ api = self._get_user_api()
183
+ if not api:
184
+ return 1
185
+
186
+ try:
187
+
188
+ async def run():
189
+ # stream_and_fetch_swarms doesn't take cluster_id parameter
190
+ swarms = await api.stream_and_fetch_swarms()
191
+ # Filter by cluster_id if provided (swarms are dicts)
192
+ if cluster_id:
193
+ swarms = [s for s in swarms if s.get("cluster_id") == cluster_id]
194
+ return swarms
195
+
196
+ message = f"Fetching swarms{f' for cluster {cluster_id}' if cluster_id else ''}..."
197
+ with Progress(
198
+ SpinnerColumn(),
199
+ TextColumn("[progress.description]{task.description}"),
200
+ console=self.console,
201
+ ) as progress:
202
+ task = progress.add_task(message, total=None)
203
+ swarms = asyncio.run(run())
204
+ progress.update(task, completed=True)
205
+
206
+ # Display swarms
207
+ if not swarms:
208
+ self.print("No swarms found.")
209
+ return 0
210
+
211
+ table = Table(
212
+ title=f"User Swarms{f' (Cluster: {cluster_id})' if cluster_id else ''}"
213
+ )
214
+ table.add_column("Swarm ID", style="cyan")
215
+ table.add_column("Name", style="blue")
216
+ table.add_column("Status", style="green")
217
+ table.add_column("Cluster", style="magenta")
218
+ table.add_column("Created", style="yellow")
219
+
220
+ for swarm in swarms:
221
+ table.add_row(
222
+ swarm.get("swarm_id", "Unknown"),
223
+ swarm.get("name", "Unknown"),
224
+ swarm.get("status", "Unknown"),
225
+ swarm.get("cluster_id", "Unknown"),
226
+ swarm.get("created_at", "Unknown"),
227
+ )
228
+
229
+ self.console.print(table)
230
+ return 0
231
+
232
+ except Exception as e:
233
+ self.print_error(f"Failed to list swarms: {e}")
234
+ return 1