pltr-cli 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,508 @@
1
+ """
2
+ Ontology commands for interacting with Foundry ontologies.
3
+ """
4
+
5
+ import json
6
+ import typer
7
+ from typing import Optional
8
+ from rich.console import Console
9
+
10
+ from ..services.ontology import (
11
+ OntologyService,
12
+ ObjectTypeService,
13
+ OntologyObjectService,
14
+ ActionService,
15
+ QueryService,
16
+ )
17
+ from ..utils.formatting import OutputFormatter
18
+ from ..utils.progress import SpinnerProgressTracker
19
+ from ..auth.base import ProfileNotFoundError, MissingCredentialsError
20
+
21
+ app = typer.Typer(help="Ontology operations")
22
+ console = Console()
23
+ formatter = OutputFormatter(console)
24
+
25
+
26
+ # Ontology management commands
27
+ @app.command("list")
28
+ def list_ontologies(
29
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
30
+ format: str = typer.Option(
31
+ "table", "--format", "-f", help="Output format (table, json, csv)"
32
+ ),
33
+ output: Optional[str] = typer.Option(
34
+ None, "--output", "-o", help="Output file path"
35
+ ),
36
+ page_size: Optional[int] = typer.Option(
37
+ None, "--page-size", help="Number of results per page"
38
+ ),
39
+ ):
40
+ """List all available ontologies."""
41
+ try:
42
+ service = OntologyService(profile=profile)
43
+
44
+ with SpinnerProgressTracker().track_spinner("Fetching ontologies..."):
45
+ ontologies = service.list_ontologies(page_size=page_size)
46
+
47
+ formatter.format_table(
48
+ ontologies,
49
+ columns=["rid", "api_name", "display_name", "description"],
50
+ format=format,
51
+ output=output,
52
+ )
53
+
54
+ if output:
55
+ formatter.print_success(f"Ontologies saved to {output}")
56
+
57
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
58
+ formatter.print_error(f"Authentication error: {e}")
59
+ raise typer.Exit(1)
60
+ except Exception as e:
61
+ formatter.print_error(f"Failed to list ontologies: {e}")
62
+ raise typer.Exit(1)
63
+
64
+
65
+ @app.command("get")
66
+ def get_ontology(
67
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
68
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
69
+ format: str = typer.Option(
70
+ "table", "--format", "-f", help="Output format (table, json, csv)"
71
+ ),
72
+ output: Optional[str] = typer.Option(
73
+ None, "--output", "-o", help="Output file path"
74
+ ),
75
+ ):
76
+ """Get details of a specific ontology."""
77
+ try:
78
+ service = OntologyService(profile=profile)
79
+
80
+ with SpinnerProgressTracker().track_spinner(
81
+ f"Fetching ontology {ontology_rid}..."
82
+ ):
83
+ ontology = service.get_ontology(ontology_rid)
84
+
85
+ formatter.format_dict(ontology, format=format, output=output)
86
+
87
+ if output:
88
+ formatter.print_success(f"Ontology information saved to {output}")
89
+
90
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
91
+ formatter.print_error(f"Authentication error: {e}")
92
+ raise typer.Exit(1)
93
+ except Exception as e:
94
+ formatter.print_error(f"Failed to get ontology: {e}")
95
+ raise typer.Exit(1)
96
+
97
+
98
+ # Object Type commands
99
+ @app.command("object-type-list")
100
+ def list_object_types(
101
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
102
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
103
+ format: str = typer.Option(
104
+ "table", "--format", "-f", help="Output format (table, json, csv)"
105
+ ),
106
+ output: Optional[str] = typer.Option(
107
+ None, "--output", "-o", help="Output file path"
108
+ ),
109
+ page_size: Optional[int] = typer.Option(
110
+ None, "--page-size", help="Number of results per page"
111
+ ),
112
+ ):
113
+ """List object types in an ontology."""
114
+ try:
115
+ service = ObjectTypeService(profile=profile)
116
+
117
+ with SpinnerProgressTracker().track_spinner("Fetching object types..."):
118
+ object_types = service.list_object_types(ontology_rid, page_size=page_size)
119
+
120
+ formatter.format_table(
121
+ object_types,
122
+ columns=["api_name", "display_name", "description", "primary_key"],
123
+ format=format,
124
+ output=output,
125
+ )
126
+
127
+ if output:
128
+ formatter.print_success(f"Object types saved to {output}")
129
+
130
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
131
+ formatter.print_error(f"Authentication error: {e}")
132
+ raise typer.Exit(1)
133
+ except Exception as e:
134
+ formatter.print_error(f"Failed to list object types: {e}")
135
+ raise typer.Exit(1)
136
+
137
+
138
+ @app.command("object-type-get")
139
+ def get_object_type(
140
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
141
+ object_type: str = typer.Argument(..., help="Object type API name"),
142
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
143
+ format: str = typer.Option(
144
+ "table", "--format", "-f", help="Output format (table, json, csv)"
145
+ ),
146
+ output: Optional[str] = typer.Option(
147
+ None, "--output", "-o", help="Output file path"
148
+ ),
149
+ ):
150
+ """Get details of a specific object type."""
151
+ try:
152
+ service = ObjectTypeService(profile=profile)
153
+
154
+ with SpinnerProgressTracker().track_spinner(
155
+ f"Fetching object type {object_type}..."
156
+ ):
157
+ obj_type = service.get_object_type(ontology_rid, object_type)
158
+
159
+ formatter.format_dict(obj_type, format=format, output=output)
160
+
161
+ if output:
162
+ formatter.print_success(f"Object type information saved to {output}")
163
+
164
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
165
+ formatter.print_error(f"Authentication error: {e}")
166
+ raise typer.Exit(1)
167
+ except Exception as e:
168
+ formatter.print_error(f"Failed to get object type: {e}")
169
+ raise typer.Exit(1)
170
+
171
+
172
+ # Object operations
173
+ @app.command("object-list")
174
+ def list_objects(
175
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
176
+ object_type: str = typer.Argument(..., help="Object type API name"),
177
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
178
+ format: str = typer.Option(
179
+ "table", "--format", "-f", help="Output format (table, json, csv)"
180
+ ),
181
+ output: Optional[str] = typer.Option(
182
+ None, "--output", "-o", help="Output file path"
183
+ ),
184
+ page_size: Optional[int] = typer.Option(
185
+ None, "--page-size", help="Number of results per page"
186
+ ),
187
+ properties: Optional[str] = typer.Option(
188
+ None, "--properties", help="Comma-separated list of properties to include"
189
+ ),
190
+ ):
191
+ """List objects of a specific type."""
192
+ try:
193
+ service = OntologyObjectService(profile=profile)
194
+
195
+ prop_list = properties.split(",") if properties else None
196
+
197
+ with SpinnerProgressTracker().track_spinner(
198
+ f"Fetching {object_type} objects..."
199
+ ):
200
+ objects = service.list_objects(
201
+ ontology_rid, object_type, page_size=page_size, properties=prop_list
202
+ )
203
+
204
+ if format == "table" and objects:
205
+ # Use first object's keys as columns
206
+ columns = list(objects[0].keys()) if objects else []
207
+ formatter.format_table(
208
+ objects, columns=columns, format=format, output=output
209
+ )
210
+ else:
211
+ formatter.format_list(objects, format=format, output=output)
212
+
213
+ if output:
214
+ formatter.print_success(f"Objects saved to {output}")
215
+
216
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
217
+ formatter.print_error(f"Authentication error: {e}")
218
+ raise typer.Exit(1)
219
+ except Exception as e:
220
+ formatter.print_error(f"Failed to list objects: {e}")
221
+ raise typer.Exit(1)
222
+
223
+
224
+ @app.command("object-get")
225
+ def get_object(
226
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
227
+ object_type: str = typer.Argument(..., help="Object type API name"),
228
+ primary_key: str = typer.Argument(..., help="Object primary key"),
229
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
230
+ format: str = typer.Option(
231
+ "table", "--format", "-f", help="Output format (table, json, csv)"
232
+ ),
233
+ output: Optional[str] = typer.Option(
234
+ None, "--output", "-o", help="Output file path"
235
+ ),
236
+ properties: Optional[str] = typer.Option(
237
+ None, "--properties", help="Comma-separated list of properties to include"
238
+ ),
239
+ ):
240
+ """Get a specific object by primary key."""
241
+ try:
242
+ service = OntologyObjectService(profile=profile)
243
+
244
+ prop_list = properties.split(",") if properties else None
245
+
246
+ with SpinnerProgressTracker().track_spinner(
247
+ f"Fetching object {primary_key}..."
248
+ ):
249
+ obj = service.get_object(
250
+ ontology_rid, object_type, primary_key, properties=prop_list
251
+ )
252
+
253
+ formatter.format_dict(obj, format=format, output=output)
254
+
255
+ if output:
256
+ formatter.print_success(f"Object information saved to {output}")
257
+
258
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
259
+ formatter.print_error(f"Authentication error: {e}")
260
+ raise typer.Exit(1)
261
+ except Exception as e:
262
+ formatter.print_error(f"Failed to get object: {e}")
263
+ raise typer.Exit(1)
264
+
265
+
266
+ @app.command("object-aggregate")
267
+ def aggregate_objects(
268
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
269
+ object_type: str = typer.Argument(..., help="Object type API name"),
270
+ aggregations: str = typer.Argument(..., help="JSON string of aggregation specs"),
271
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
272
+ format: str = typer.Option(
273
+ "table", "--format", "-f", help="Output format (table, json, csv)"
274
+ ),
275
+ output: Optional[str] = typer.Option(
276
+ None, "--output", "-o", help="Output file path"
277
+ ),
278
+ group_by: Optional[str] = typer.Option(
279
+ None, "--group-by", help="Comma-separated list of fields to group by"
280
+ ),
281
+ filter: Optional[str] = typer.Option(
282
+ None, "--filter", help="JSON string of filter criteria"
283
+ ),
284
+ ):
285
+ """Aggregate objects with specified functions."""
286
+ try:
287
+ service = OntologyObjectService(profile=profile)
288
+
289
+ # Parse JSON inputs
290
+ agg_list = json.loads(aggregations)
291
+ group_list = group_by.split(",") if group_by else None
292
+ filter_dict = json.loads(filter) if filter else None
293
+
294
+ with SpinnerProgressTracker().track_spinner("Aggregating objects..."):
295
+ result = service.aggregate_objects(
296
+ ontology_rid,
297
+ object_type,
298
+ agg_list,
299
+ group_by=group_list,
300
+ filter=filter_dict,
301
+ )
302
+
303
+ formatter.format_dict(result, format=format, output=output)
304
+
305
+ if output:
306
+ formatter.print_success(f"Aggregation results saved to {output}")
307
+
308
+ except json.JSONDecodeError as e:
309
+ formatter.print_error(f"Invalid JSON: {e}")
310
+ raise typer.Exit(1)
311
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
312
+ formatter.print_error(f"Authentication error: {e}")
313
+ raise typer.Exit(1)
314
+ except Exception as e:
315
+ formatter.print_error(f"Failed to aggregate objects: {e}")
316
+ raise typer.Exit(1)
317
+
318
+
319
+ @app.command("object-linked")
320
+ def list_linked_objects(
321
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
322
+ object_type: str = typer.Argument(..., help="Object type API name"),
323
+ primary_key: str = typer.Argument(..., help="Object primary key"),
324
+ link_type: str = typer.Argument(..., help="Link type API name"),
325
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
326
+ format: str = typer.Option(
327
+ "table", "--format", "-f", help="Output format (table, json, csv)"
328
+ ),
329
+ output: Optional[str] = typer.Option(
330
+ None, "--output", "-o", help="Output file path"
331
+ ),
332
+ page_size: Optional[int] = typer.Option(
333
+ None, "--page-size", help="Number of results per page"
334
+ ),
335
+ properties: Optional[str] = typer.Option(
336
+ None, "--properties", help="Comma-separated list of properties to include"
337
+ ),
338
+ ):
339
+ """List objects linked to a specific object."""
340
+ try:
341
+ service = OntologyObjectService(profile=profile)
342
+
343
+ prop_list = properties.split(",") if properties else None
344
+
345
+ with SpinnerProgressTracker().track_spinner("Fetching linked objects..."):
346
+ objects = service.list_linked_objects(
347
+ ontology_rid,
348
+ object_type,
349
+ primary_key,
350
+ link_type,
351
+ page_size=page_size,
352
+ properties=prop_list,
353
+ )
354
+
355
+ if format == "table" and objects:
356
+ # Use first object's keys as columns
357
+ columns = list(objects[0].keys()) if objects else []
358
+ formatter.format_table(
359
+ objects, columns=columns, format=format, output=output
360
+ )
361
+ else:
362
+ formatter.format_list(objects, format=format, output=output)
363
+
364
+ if output:
365
+ formatter.print_success(f"Linked objects saved to {output}")
366
+
367
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
368
+ formatter.print_error(f"Authentication error: {e}")
369
+ raise typer.Exit(1)
370
+ except Exception as e:
371
+ formatter.print_error(f"Failed to list linked objects: {e}")
372
+ raise typer.Exit(1)
373
+
374
+
375
+ # Action commands
376
+ @app.command("action-apply")
377
+ def apply_action(
378
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
379
+ action_type: str = typer.Argument(..., help="Action type API name"),
380
+ parameters: str = typer.Argument(..., help="JSON string of action parameters"),
381
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
382
+ format: str = typer.Option(
383
+ "table", "--format", "-f", help="Output format (table, json, csv)"
384
+ ),
385
+ output: Optional[str] = typer.Option(
386
+ None, "--output", "-o", help="Output file path"
387
+ ),
388
+ ):
389
+ """Apply an action with given parameters."""
390
+ try:
391
+ service = ActionService(profile=profile)
392
+
393
+ # Parse JSON parameters
394
+ params = json.loads(parameters)
395
+
396
+ with SpinnerProgressTracker().track_spinner(
397
+ f"Applying action {action_type}..."
398
+ ):
399
+ result = service.apply_action(ontology_rid, action_type, params)
400
+
401
+ formatter.format_dict(result, format=format, output=output)
402
+
403
+ if output:
404
+ formatter.print_success(f"Action result saved to {output}")
405
+
406
+ except json.JSONDecodeError as e:
407
+ formatter.print_error(f"Invalid JSON: {e}")
408
+ raise typer.Exit(1)
409
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
410
+ formatter.print_error(f"Authentication error: {e}")
411
+ raise typer.Exit(1)
412
+ except Exception as e:
413
+ formatter.print_error(f"Failed to apply action: {e}")
414
+ raise typer.Exit(1)
415
+
416
+
417
+ @app.command("action-validate")
418
+ def validate_action(
419
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
420
+ action_type: str = typer.Argument(..., help="Action type API name"),
421
+ parameters: str = typer.Argument(..., help="JSON string of action parameters"),
422
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
423
+ format: str = typer.Option(
424
+ "table", "--format", "-f", help="Output format (table, json, csv)"
425
+ ),
426
+ output: Optional[str] = typer.Option(
427
+ None, "--output", "-o", help="Output file path"
428
+ ),
429
+ ):
430
+ """Validate action parameters without executing."""
431
+ try:
432
+ service = ActionService(profile=profile)
433
+
434
+ # Parse JSON parameters
435
+ params = json.loads(parameters)
436
+
437
+ with SpinnerProgressTracker().track_spinner(
438
+ f"Validating action {action_type}..."
439
+ ):
440
+ result = service.validate_action(ontology_rid, action_type, params)
441
+
442
+ formatter.format_dict(result, format=format, output=output)
443
+
444
+ if result.get("valid"):
445
+ formatter.print_success("Action parameters are valid")
446
+ else:
447
+ formatter.print_error("Action parameters are invalid")
448
+
449
+ if output:
450
+ formatter.print_success(f"Validation result saved to {output}")
451
+
452
+ except json.JSONDecodeError as e:
453
+ formatter.print_error(f"Invalid JSON: {e}")
454
+ raise typer.Exit(1)
455
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
456
+ formatter.print_error(f"Authentication error: {e}")
457
+ raise typer.Exit(1)
458
+ except Exception as e:
459
+ formatter.print_error(f"Failed to validate action: {e}")
460
+ raise typer.Exit(1)
461
+
462
+
463
+ # Query commands
464
+ @app.command("query-execute")
465
+ def execute_query(
466
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
467
+ query_name: str = typer.Argument(..., help="Query API name"),
468
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
469
+ format: str = typer.Option(
470
+ "table", "--format", "-f", help="Output format (table, json, csv)"
471
+ ),
472
+ output: Optional[str] = typer.Option(
473
+ None, "--output", "-o", help="Output file path"
474
+ ),
475
+ parameters: Optional[str] = typer.Option(
476
+ None, "--parameters", help="JSON string of query parameters"
477
+ ),
478
+ ):
479
+ """Execute a predefined query."""
480
+ try:
481
+ service = QueryService(profile=profile)
482
+
483
+ # Parse JSON parameters if provided
484
+ params = json.loads(parameters) if parameters else None
485
+
486
+ with SpinnerProgressTracker().track_spinner(f"Executing query {query_name}..."):
487
+ result = service.execute_query(ontology_rid, query_name, parameters=params)
488
+
489
+ # Handle different result formats
490
+ if "rows" in result:
491
+ formatter.format_list(result["rows"], format=format, output=output)
492
+ elif "objects" in result:
493
+ formatter.format_list(result["objects"], format=format, output=output)
494
+ else:
495
+ formatter.format_dict(result, format=format, output=output)
496
+
497
+ if output:
498
+ formatter.print_success(f"Query results saved to {output}")
499
+
500
+ except json.JSONDecodeError as e:
501
+ formatter.print_error(f"Invalid JSON: {e}")
502
+ raise typer.Exit(1)
503
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
504
+ formatter.print_error(f"Authentication error: {e}")
505
+ raise typer.Exit(1)
506
+ except Exception as e:
507
+ formatter.print_error(f"Failed to execute query: {e}")
508
+ raise typer.Exit(1)
pltr/commands/shell.py ADDED
@@ -0,0 +1,126 @@
1
+ """
2
+ Interactive shell (REPL) command for the pltr CLI.
3
+ Provides an interactive mode with tab completion and command history.
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import typer
11
+ from click_repl import repl # type: ignore
12
+ from prompt_toolkit.history import FileHistory
13
+ from rich.console import Console
14
+
15
+ from ..config.profiles import ProfileManager
16
+
17
+ shell_app = typer.Typer(
18
+ name="shell",
19
+ help="Start an interactive shell session with tab completion and history",
20
+ )
21
+
22
+
23
+ def get_history_file() -> Path:
24
+ """Get the path to the history file for the REPL."""
25
+ config_dir = Path.home() / ".config" / "pltr"
26
+ config_dir.mkdir(parents=True, exist_ok=True)
27
+ return config_dir / "repl_history"
28
+
29
+
30
+ def get_prompt() -> str:
31
+ """Get the prompt string for the REPL."""
32
+ try:
33
+ profile_manager = ProfileManager()
34
+ current_profile = profile_manager.get_active_profile()
35
+ if current_profile:
36
+ return f"pltr ({current_profile})> "
37
+ else:
38
+ return "pltr> "
39
+ except Exception:
40
+ return "pltr> "
41
+
42
+
43
+ @shell_app.command()
44
+ def start(
45
+ profile: Optional[str] = typer.Option(
46
+ None, "--profile", help="Auth profile to use for the session"
47
+ ),
48
+ ) -> None:
49
+ """
50
+ Start an interactive shell session for pltr CLI.
51
+
52
+ Features:
53
+ - Tab completion for all commands
54
+ - Command history (persistent across sessions)
55
+ - Current profile displayed in prompt
56
+ - All pltr commands available without the 'pltr' prefix
57
+
58
+ Examples:
59
+ # Start interactive shell
60
+ $ pltr shell
61
+
62
+ # In the shell, run commands without 'pltr' prefix:
63
+ pltr> dataset get ri.foundry.main.dataset.123
64
+ pltr> ontology list
65
+ pltr> sql execute "SELECT * FROM dataset LIMIT 10"
66
+
67
+ # Exit the shell:
68
+ pltr> exit
69
+ """
70
+ console = Console()
71
+
72
+ # Set profile if specified
73
+ if profile:
74
+ os.environ["PLTR_PROFILE"] = profile
75
+ console.print(f"[green]Using profile: {profile}[/green]")
76
+
77
+ # Welcome message
78
+ console.print("\n[bold cyan]Welcome to pltr interactive shell![/bold cyan]")
79
+ console.print("Type 'help' for available commands, 'exit' to quit.\n")
80
+
81
+ # Import here to avoid circular dependency
82
+ from ..cli import app as main_app
83
+
84
+ # Convert Typer app to Click object and create context
85
+ # This is the correct way to integrate click-repl with Typer
86
+ from typer.main import get_command
87
+
88
+ click_app = get_command(main_app)
89
+ ctx = click_app.make_context("pltr", [])
90
+
91
+ # Start the REPL with the Click context
92
+ repl(
93
+ ctx,
94
+ prompt_kwargs={
95
+ "message": get_prompt,
96
+ "history": FileHistory(str(get_history_file())),
97
+ "complete_while_typing": True,
98
+ "enable_history_search": True,
99
+ },
100
+ )
101
+
102
+ console.print("\n[cyan]Goodbye![/cyan]")
103
+
104
+
105
+ # Make 'start' the default command when just running 'pltr shell'
106
+ @shell_app.callback(invoke_without_command=True)
107
+ def shell_callback(
108
+ ctx: typer.Context,
109
+ profile: Optional[str] = typer.Option(
110
+ None, "--profile", help="Auth profile to use for the session"
111
+ ),
112
+ ) -> None:
113
+ """Interactive shell mode with tab completion and command history."""
114
+ if ctx.invoked_subcommand is None:
115
+ start(profile=profile)
116
+
117
+
118
+ # Alternative command name for convenience
119
+ @shell_app.command("interactive", hidden=True)
120
+ def interactive_alias(
121
+ profile: Optional[str] = typer.Option(
122
+ None, "--profile", help="Auth profile to use for the session"
123
+ ),
124
+ ) -> None:
125
+ """Alias for 'start' command."""
126
+ start(profile=profile)