qbraid-cli 0.8.5a1__py3-none-any.whl → 0.12.0a0__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.
- qbraid_cli/_version.py +16 -14
- qbraid_cli/account/__init__.py +11 -0
- qbraid_cli/account/app.py +67 -0
- qbraid_cli/admin/app.py +21 -13
- qbraid_cli/admin/headers.py +132 -23
- qbraid_cli/admin/validation.py +1 -8
- qbraid_cli/chat/__init__.py +11 -0
- qbraid_cli/chat/app.py +76 -0
- qbraid_cli/configure/actions.py +21 -2
- qbraid_cli/configure/app.py +147 -2
- qbraid_cli/configure/claude_config.py +215 -0
- qbraid_cli/devices/app.py +27 -4
- qbraid_cli/envs/activate.py +38 -8
- qbraid_cli/envs/app.py +716 -89
- qbraid_cli/envs/create.py +3 -2
- qbraid_cli/files/__init__.py +11 -0
- qbraid_cli/files/app.py +139 -0
- qbraid_cli/handlers.py +35 -5
- qbraid_cli/jobs/app.py +33 -13
- qbraid_cli/jobs/toggle_braket.py +2 -13
- qbraid_cli/jobs/validation.py +1 -0
- qbraid_cli/kernels/app.py +4 -3
- qbraid_cli/main.py +57 -13
- qbraid_cli/mcp/__init__.py +10 -0
- qbraid_cli/mcp/app.py +126 -0
- qbraid_cli/mcp/serve.py +321 -0
- qbraid_cli/pip/app.py +2 -2
- qbraid_cli/pip/hooks.py +1 -0
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info}/METADATA +37 -14
- qbraid_cli-0.12.0a0.dist-info/RECORD +46 -0
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info}/WHEEL +1 -1
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info/licenses}/LICENSE +6 -4
- qbraid_cli/admin/buildlogs.py +0 -114
- qbraid_cli/credits/__init__.py +0 -11
- qbraid_cli/credits/app.py +0 -35
- qbraid_cli-0.8.5a1.dist-info/RECORD +0 -39
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info}/entry_points.txt +0 -0
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info}/top_level.txt +0 -0
qbraid_cli/envs/create.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Module supporting 'qbraid envs create' command.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
+
from qbraid_core.services.environments.schema import EnvironmentConfig
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def create_venv(*args, **kwargs) -> None:
|
|
@@ -21,9 +22,9 @@ def update_state_json(*ags, **kwargs) -> None:
|
|
|
21
22
|
return update_state(*ags, **kwargs)
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
def create_qbraid_env_assets(
|
|
25
|
+
def create_qbraid_env_assets(env_id: str, env_id_path: str, env_config: EnvironmentConfig) -> None:
|
|
25
26
|
"""Create a qBraid environment including python venv, PS1 configs,
|
|
26
27
|
kernel resource files, and qBraid state.json."""
|
|
27
28
|
from qbraid_core.services.environments.create import create_qbraid_env_assets as create_assets
|
|
28
29
|
|
|
29
|
-
return create_assets(
|
|
30
|
+
return create_assets(env_id, env_id_path, env_config)
|
qbraid_cli/files/app.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module defining commands in the 'qbraid files' namespace.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import rich
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from qbraid_cli.handlers import handle_error, run_progress_task
|
|
16
|
+
|
|
17
|
+
files_app = typer.Typer(help="Manage qBraid cloud storage files.", no_args_is_help=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_file_less_than_10mb(file_path: Path) -> bool:
|
|
21
|
+
"""
|
|
22
|
+
Check if the given file is less than 10MB in size.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
file_path (Path): The path to the file to check.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
bool: True if the file is less than 10MB, False otherwise.
|
|
29
|
+
"""
|
|
30
|
+
ten_mb = 10485760 # 10 MB in bytes (10 * 1024 * 1024)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
return file_path.stat().st_size < ten_mb
|
|
34
|
+
except OSError:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@files_app.command(name="upload")
|
|
39
|
+
def files_upload(
|
|
40
|
+
filepath: Path = typer.Argument(
|
|
41
|
+
...,
|
|
42
|
+
exists=True,
|
|
43
|
+
dir_okay=False,
|
|
44
|
+
resolve_path=True,
|
|
45
|
+
help="Local path to the file to upload.",
|
|
46
|
+
),
|
|
47
|
+
namespace: str = typer.Option(
|
|
48
|
+
"user",
|
|
49
|
+
"--namespace",
|
|
50
|
+
"-n",
|
|
51
|
+
help="Target qBraid namespace for the upload.",
|
|
52
|
+
),
|
|
53
|
+
object_path: str = typer.Option(
|
|
54
|
+
None,
|
|
55
|
+
"--object-path",
|
|
56
|
+
"-p",
|
|
57
|
+
help=("Target object path. " "Defaults to original filename in namespace root."),
|
|
58
|
+
),
|
|
59
|
+
overwrite: bool = typer.Option(
|
|
60
|
+
False,
|
|
61
|
+
"--overwrite",
|
|
62
|
+
"-o",
|
|
63
|
+
help="Overwrite existing file if it already exists in the target location.",
|
|
64
|
+
),
|
|
65
|
+
):
|
|
66
|
+
"""Upload a local file to qBraid storage."""
|
|
67
|
+
|
|
68
|
+
if not is_file_less_than_10mb(filepath):
|
|
69
|
+
handle_error("Error", "File too large. Must be less than 10MB for direct upload.")
|
|
70
|
+
|
|
71
|
+
def upload_file() -> dict[str, Any]:
|
|
72
|
+
from qbraid_core.services.storage import FileStorageClient
|
|
73
|
+
|
|
74
|
+
client = FileStorageClient()
|
|
75
|
+
data = client.upload_file(
|
|
76
|
+
filepath, namespace=namespace, object_path=object_path, overwrite=overwrite
|
|
77
|
+
)
|
|
78
|
+
return data
|
|
79
|
+
|
|
80
|
+
data: dict = run_progress_task(
|
|
81
|
+
upload_file, description="Uploading file...", include_error_traceback=False
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
rich.print("File uploaded successfully!")
|
|
85
|
+
namespace = data.get("namespace")
|
|
86
|
+
object_path = data.get("objectPath")
|
|
87
|
+
|
|
88
|
+
if namespace and object_path:
|
|
89
|
+
rich.print(f"\nNamespace: '{namespace}'")
|
|
90
|
+
rich.print(f"Object path: '{object_path}'")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@files_app.command(name="download")
|
|
94
|
+
def files_download(
|
|
95
|
+
object_path: str = typer.Argument(
|
|
96
|
+
...,
|
|
97
|
+
help="The folder + filename describing the file to download.",
|
|
98
|
+
),
|
|
99
|
+
namespace: str = typer.Option(
|
|
100
|
+
"user",
|
|
101
|
+
"--namespace",
|
|
102
|
+
"-n",
|
|
103
|
+
help="Source qBraid namespace for the download.",
|
|
104
|
+
),
|
|
105
|
+
save_path: Path = typer.Option(
|
|
106
|
+
Path.cwd(),
|
|
107
|
+
"--save-path",
|
|
108
|
+
"-s",
|
|
109
|
+
resolve_path=True,
|
|
110
|
+
help="Local directory to save the downloaded file.",
|
|
111
|
+
),
|
|
112
|
+
overwrite: bool = typer.Option(
|
|
113
|
+
False,
|
|
114
|
+
"--overwrite",
|
|
115
|
+
"-o",
|
|
116
|
+
help="Overwrite existing file if it already exists in the target location.",
|
|
117
|
+
),
|
|
118
|
+
):
|
|
119
|
+
"""Download a file from qBraid storage."""
|
|
120
|
+
|
|
121
|
+
def download_file() -> Path:
|
|
122
|
+
from qbraid_core.services.storage import FileStorageClient
|
|
123
|
+
|
|
124
|
+
client = FileStorageClient()
|
|
125
|
+
file_path = client.download_file(
|
|
126
|
+
object_path, namespace=namespace, save_path=save_path, overwrite=overwrite
|
|
127
|
+
)
|
|
128
|
+
return file_path
|
|
129
|
+
|
|
130
|
+
file_path: Path = run_progress_task(
|
|
131
|
+
download_file, description="Downloading file...", include_error_traceback=False
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
rich.print("File downloaded successfully!")
|
|
135
|
+
rich.print(f"Saved to: '{str(file_path)}'")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
files_app()
|
qbraid_cli/handlers.py
CHANGED
|
@@ -10,11 +10,12 @@ and executing operations with progress tracking within the qBraid CLI.
|
|
|
10
10
|
import os
|
|
11
11
|
import traceback
|
|
12
12
|
from pathlib import Path
|
|
13
|
+
from time import sleep
|
|
13
14
|
from typing import Any, Callable, Optional, Union
|
|
14
15
|
|
|
15
16
|
import typer
|
|
16
17
|
from rich.console import Console
|
|
17
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
18
|
+
from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn
|
|
18
19
|
|
|
19
20
|
from .exceptions import DEFAULT_ERROR_MESSAGE, QbraidException
|
|
20
21
|
|
|
@@ -24,6 +25,14 @@ def _should_display_progress():
|
|
|
24
25
|
return os.getenv("QBRAID_CLI_SHOW_PROGRESS", "true").lower() in ["true", "1", "t", "y", "yes"]
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def _update_completed_task(
|
|
29
|
+
progress: Progress, task_id: TaskID, success: bool = True, sleep_time: float = 0.15
|
|
30
|
+
):
|
|
31
|
+
status = "Done" if success else "Failed"
|
|
32
|
+
progress.update(task_id, completed=100, status=status)
|
|
33
|
+
sleep(sleep_time)
|
|
34
|
+
|
|
35
|
+
|
|
27
36
|
def handle_error(
|
|
28
37
|
error_type: Optional[str] = None, message: Optional[str] = None, include_traceback: bool = True
|
|
29
38
|
) -> None:
|
|
@@ -80,11 +89,28 @@ def handle_filesystem_operation(operation: Callable[[], None], path: Path) -> No
|
|
|
80
89
|
raise QbraidException(f"Failed to save configuration to {path}: {err.strerror}") from err
|
|
81
90
|
|
|
82
91
|
|
|
92
|
+
def print_formatted_data(data: Any, fmt: bool = True) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Print data with optional formatting using rich console.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
data (Any): The data to be printed.
|
|
98
|
+
fmt (bool): If True, use rich console formatting. If False, use standard print.
|
|
99
|
+
Defaults to True.
|
|
100
|
+
"""
|
|
101
|
+
if fmt:
|
|
102
|
+
console = Console()
|
|
103
|
+
console.print(data)
|
|
104
|
+
else:
|
|
105
|
+
print(data)
|
|
106
|
+
|
|
107
|
+
|
|
83
108
|
def run_progress_task(
|
|
84
109
|
operation: Callable[..., Any],
|
|
85
110
|
*args,
|
|
86
111
|
description: Optional[str] = None,
|
|
87
112
|
error_message: Optional[str] = None,
|
|
113
|
+
include_error_traceback: bool = True,
|
|
88
114
|
**kwargs,
|
|
89
115
|
) -> Any:
|
|
90
116
|
"""
|
|
@@ -102,6 +128,8 @@ def run_progress_task(
|
|
|
102
128
|
error_message (optional, str): Custom error message to display if the operation.
|
|
103
129
|
fails. Defaults to None, in which case the
|
|
104
130
|
exception's message is used.
|
|
131
|
+
include_error_traceback (bool): Whether to include the traceback in the error message.
|
|
132
|
+
Defaults to True.
|
|
105
133
|
**kwargs: Arbitrary keyword arguments for the operation.
|
|
106
134
|
|
|
107
135
|
Returns:
|
|
@@ -115,7 +143,7 @@ def run_progress_task(
|
|
|
115
143
|
return operation(*args, **kwargs)
|
|
116
144
|
except Exception as err: # pylint: disable=broad-exception-caught
|
|
117
145
|
custom_message = error_message if error_message else str(err)
|
|
118
|
-
return handle_error(message=custom_message)
|
|
146
|
+
return handle_error(message=custom_message, include_traceback=include_error_traceback)
|
|
119
147
|
|
|
120
148
|
console = Console()
|
|
121
149
|
with Progress(
|
|
@@ -128,12 +156,14 @@ def run_progress_task(
|
|
|
128
156
|
task = progress.add_task(description, status="In Progress", total=None)
|
|
129
157
|
try:
|
|
130
158
|
result = operation(*args, **kwargs)
|
|
131
|
-
progress
|
|
159
|
+
_update_completed_task(progress, task, success=True)
|
|
132
160
|
return result
|
|
133
161
|
except Exception as err: # pylint: disable=broad-exception-caught
|
|
134
|
-
progress
|
|
162
|
+
_update_completed_task(progress, task, success=False)
|
|
135
163
|
custom_message = error_message if error_message else str(err)
|
|
136
|
-
return handle_error(message=custom_message)
|
|
164
|
+
return handle_error(message=custom_message, include_traceback=include_error_traceback)
|
|
165
|
+
finally:
|
|
166
|
+
progress.remove_task(task)
|
|
137
167
|
|
|
138
168
|
|
|
139
169
|
def _format_list_items(items: list[str]) -> str:
|
qbraid_cli/jobs/app.py
CHANGED
|
@@ -5,16 +5,17 @@
|
|
|
5
5
|
Module defining commands in the 'qbraid jobs' namespace.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
from typing import Any, Callable
|
|
9
10
|
|
|
10
11
|
import typer
|
|
11
12
|
from rich.console import Console
|
|
12
13
|
|
|
13
|
-
from qbraid_cli.handlers import handle_error, run_progress_task
|
|
14
|
+
from qbraid_cli.handlers import handle_error, print_formatted_data, run_progress_task
|
|
14
15
|
from qbraid_cli.jobs.toggle_braket import disable_braket, enable_braket
|
|
15
16
|
from qbraid_cli.jobs.validation import handle_jobs_state, run_progress_get_state, validate_library
|
|
16
17
|
|
|
17
|
-
jobs_app = typer.Typer(help="Manage qBraid quantum jobs.")
|
|
18
|
+
jobs_app = typer.Typer(help="Manage qBraid quantum jobs.", no_args_is_help=True)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@jobs_app.command(name="enable")
|
|
@@ -63,7 +64,7 @@ def jobs_state(
|
|
|
63
64
|
default=None,
|
|
64
65
|
help="Optional: Specify a software library with quantum jobs support to check its status.",
|
|
65
66
|
callback=validate_library,
|
|
66
|
-
)
|
|
67
|
+
),
|
|
67
68
|
) -> None:
|
|
68
69
|
"""Display the state of qBraid Quantum Jobs for the current environment."""
|
|
69
70
|
result: tuple[str, dict[str, tuple[bool, bool]]] = run_progress_get_state(library)
|
|
@@ -75,23 +76,25 @@ def jobs_state(
|
|
|
75
76
|
max_lib_length = max((len(lib) for lib in state_values.keys()), default=len(header_1))
|
|
76
77
|
padding = max_lib_length + 9
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
console.print(f"\n{header_1:<{padding}}{header_2}", style="bold")
|
|
80
|
-
|
|
79
|
+
output = ""
|
|
81
80
|
for lib, (installed, enabled) in state_values.items():
|
|
82
81
|
state_str = (
|
|
83
82
|
"[green]enabled"
|
|
84
83
|
if enabled and installed
|
|
85
84
|
else "[red]disabled" if installed else "[grey70]unavailable"
|
|
86
85
|
)
|
|
87
|
-
|
|
86
|
+
output += f"{lib:<{padding-1}} {state_str}\n"
|
|
87
|
+
|
|
88
|
+
console.print(f"Executable: {python_exe}")
|
|
89
|
+
console.print(f"\n{header_1:<{padding}}{header_2}", style="bold")
|
|
90
|
+
console.print(output)
|
|
88
91
|
|
|
89
92
|
|
|
90
93
|
@jobs_app.command(name="list")
|
|
91
94
|
def jobs_list(
|
|
92
95
|
limit: int = typer.Option(
|
|
93
96
|
10, "--limit", "-l", help="Limit the maximum number of results returned"
|
|
94
|
-
)
|
|
97
|
+
),
|
|
95
98
|
) -> None:
|
|
96
99
|
"""List qBraid Quantum Jobs."""
|
|
97
100
|
|
|
@@ -104,11 +107,8 @@ def jobs_list(
|
|
|
104
107
|
|
|
105
108
|
result: tuple[Any, Callable] = run_progress_task(import_jobs)
|
|
106
109
|
client, process_job_data = result
|
|
107
|
-
|
|
108
|
-
# raw_data = client.search_jobs(query={"numResults": limit})
|
|
109
|
-
raw_data = client.search_jobs(query={})
|
|
110
|
+
raw_data = client.search_jobs(query={"resultsPerPage": limit, "page": 0})
|
|
110
111
|
job_data, msg = process_job_data(raw_data)
|
|
111
|
-
job_data = job_data[:limit]
|
|
112
112
|
|
|
113
113
|
longest_job_id = max(len(item[0]) for item in job_data)
|
|
114
114
|
spacing = longest_job_id + 5
|
|
@@ -117,7 +117,7 @@ def jobs_list(
|
|
|
117
117
|
header_1 = "Job ID"
|
|
118
118
|
header_2 = "Submitted"
|
|
119
119
|
header_3 = "Status"
|
|
120
|
-
console.print(f"
|
|
120
|
+
console.print(f"[bold]{header_1.ljust(spacing)}{header_2.ljust(36)}{header_3}[/bold]")
|
|
121
121
|
for job_id, submitted, status in job_data:
|
|
122
122
|
if status == "COMPLETED":
|
|
123
123
|
status_color = "green"
|
|
@@ -145,5 +145,25 @@ def jobs_list(
|
|
|
145
145
|
handle_error(message="Failed to fetch quantum jobs.")
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
@jobs_app.command(name="get")
|
|
149
|
+
def jobs_get(
|
|
150
|
+
job_id: str = typer.Argument(..., help="The ID of the job to get."),
|
|
151
|
+
fmt: bool = typer.Option(
|
|
152
|
+
True, "--no-fmt", help="Disable rich console formatting (output raw data)"
|
|
153
|
+
),
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Get a qBraid Quantum Job."""
|
|
156
|
+
|
|
157
|
+
def get_job():
|
|
158
|
+
from qbraid_core.services.quantum import QuantumClient
|
|
159
|
+
|
|
160
|
+
client = QuantumClient()
|
|
161
|
+
return client.get_job(job_id)
|
|
162
|
+
|
|
163
|
+
data: dict[str, Any] = run_progress_task(get_job)
|
|
164
|
+
|
|
165
|
+
print_formatted_data(data, fmt)
|
|
166
|
+
|
|
167
|
+
|
|
148
168
|
if __name__ == "__main__":
|
|
149
169
|
jobs_app()
|
qbraid_cli/jobs/toggle_braket.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Module supporting 'qbraid jobs enable/disable braket' and commands.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import logging
|
|
9
10
|
import os
|
|
10
11
|
import re
|
|
@@ -102,7 +103,7 @@ def confirm_updates(
|
|
|
102
103
|
else:
|
|
103
104
|
raise ValueError(f"Invalid mode: {mode}. Expected 'enable' or 'disable'.")
|
|
104
105
|
|
|
105
|
-
typer.echo(f"
|
|
106
|
+
typer.echo(f"==> WARNING: {provider}/{core_package} package required <==")
|
|
106
107
|
if (
|
|
107
108
|
installed_version is not None
|
|
108
109
|
and target_version is not None
|
|
@@ -157,18 +158,6 @@ def enable_braket(auto_confirm: bool = False):
|
|
|
157
158
|
aws_configure_dummy() # TODO: possibly add another confirmation for writing aws config files
|
|
158
159
|
|
|
159
160
|
try:
|
|
160
|
-
subprocess.check_call(
|
|
161
|
-
[
|
|
162
|
-
python_exe,
|
|
163
|
-
"-m",
|
|
164
|
-
"pip",
|
|
165
|
-
"install",
|
|
166
|
-
"amazon-braket-sdk",
|
|
167
|
-
"--upgrade",
|
|
168
|
-
"--upgrade-strategy",
|
|
169
|
-
"eager",
|
|
170
|
-
]
|
|
171
|
-
)
|
|
172
161
|
subprocess.check_call([python_exe, "-m", "pip", "install", f"boto3=={target}"])
|
|
173
162
|
subprocess.check_call([python_exe, "-m", "pip", "uninstall", "botocore", "-y", "--quiet"])
|
|
174
163
|
subprocess.check_call(
|
qbraid_cli/jobs/validation.py
CHANGED
qbraid_cli/kernels/app.py
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
Module defining commands in the 'qbraid kernels' namespace.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import typer
|
|
9
10
|
from rich.console import Console
|
|
10
11
|
|
|
11
12
|
from qbraid_cli.handlers import handle_error
|
|
12
13
|
|
|
13
|
-
kernels_app = typer.Typer(help="Manage qBraid kernels.")
|
|
14
|
+
kernels_app = typer.Typer(help="Manage qBraid kernels.", no_args_is_help=True)
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@kernels_app.command(name="list")
|
|
@@ -55,7 +56,7 @@ def kernels_list():
|
|
|
55
56
|
def kernels_add(
|
|
56
57
|
environment: str = typer.Argument(
|
|
57
58
|
..., help="Name of environment for which to add ipykernel. Values from 'qbraid envs list'."
|
|
58
|
-
)
|
|
59
|
+
),
|
|
59
60
|
):
|
|
60
61
|
"""Add a kernel."""
|
|
61
62
|
from qbraid_core.services.environments.kernels import add_kernels
|
|
@@ -77,7 +78,7 @@ def kernels_remove(
|
|
|
77
78
|
environment: str = typer.Argument(
|
|
78
79
|
...,
|
|
79
80
|
help=("Name of environment for which to remove ipykernel. Values from 'qbraid envs list'."),
|
|
80
|
-
)
|
|
81
|
+
),
|
|
81
82
|
):
|
|
82
83
|
"""Remove a kernel."""
|
|
83
84
|
from qbraid_core.services.environments.kernels import remove_kernels
|
qbraid_cli/main.py
CHANGED
|
@@ -6,27 +6,42 @@ Entrypoint for the qBraid CLI.
|
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import click
|
|
10
|
+
import rich
|
|
9
11
|
import typer
|
|
10
12
|
|
|
11
|
-
from qbraid_cli.
|
|
12
|
-
from qbraid_cli.
|
|
13
|
-
from qbraid_cli.
|
|
14
|
-
from qbraid_cli.
|
|
15
|
-
from qbraid_cli.
|
|
16
|
-
from qbraid_cli.
|
|
17
|
-
from qbraid_cli.
|
|
18
|
-
from qbraid_cli.
|
|
13
|
+
from qbraid_cli.account import account_app
|
|
14
|
+
from qbraid_cli.admin import admin_app
|
|
15
|
+
from qbraid_cli.chat import ChatFormat, list_models_callback, prompt_callback
|
|
16
|
+
from qbraid_cli.configure import configure_app
|
|
17
|
+
from qbraid_cli.devices import devices_app
|
|
18
|
+
from qbraid_cli.files import files_app
|
|
19
|
+
from qbraid_cli.jobs import jobs_app
|
|
20
|
+
from qbraid_cli.mcp import mcp_app
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from qbraid_cli.envs import envs_app
|
|
24
|
+
from qbraid_cli.kernels import kernels_app
|
|
25
|
+
from qbraid_cli.pip import pip_app
|
|
26
|
+
|
|
27
|
+
ENVS_COMMANDS = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
ENVS_COMMANDS = False
|
|
19
30
|
|
|
20
31
|
app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
|
|
21
32
|
|
|
22
33
|
app.add_typer(admin_app, name="admin")
|
|
23
34
|
app.add_typer(configure_app, name="configure")
|
|
24
|
-
app.add_typer(
|
|
35
|
+
app.add_typer(account_app, name="account")
|
|
25
36
|
app.add_typer(devices_app, name="devices")
|
|
26
|
-
app.add_typer(
|
|
37
|
+
app.add_typer(files_app, name="files")
|
|
27
38
|
app.add_typer(jobs_app, name="jobs")
|
|
28
|
-
app.add_typer(
|
|
29
|
-
|
|
39
|
+
app.add_typer(mcp_app, name="mcp")
|
|
40
|
+
|
|
41
|
+
if ENVS_COMMANDS is True:
|
|
42
|
+
app.add_typer(envs_app, name="envs")
|
|
43
|
+
app.add_typer(kernels_app, name="kernels")
|
|
44
|
+
app.add_typer(pip_app, name="pip")
|
|
30
45
|
|
|
31
46
|
|
|
32
47
|
def version_callback(value: bool):
|
|
@@ -59,7 +74,7 @@ def show_banner():
|
|
|
59
74
|
typer.echo("")
|
|
60
75
|
typer.echo("- Use 'qbraid --version' to see the current version.")
|
|
61
76
|
typer.echo("")
|
|
62
|
-
|
|
77
|
+
rich.print("Reference Docs: https://docs.qbraid.com/cli/api-reference/qbraid")
|
|
63
78
|
|
|
64
79
|
|
|
65
80
|
@app.callback(invoke_without_command=True)
|
|
@@ -79,5 +94,34 @@ def main(
|
|
|
79
94
|
show_banner()
|
|
80
95
|
|
|
81
96
|
|
|
97
|
+
@app.command(help="Interact with qBraid AI chat service.", no_args_is_help=True)
|
|
98
|
+
def chat(
|
|
99
|
+
prompt: str = typer.Option(
|
|
100
|
+
None, "--prompt", "-p", help="The prompt to send to the chat service."
|
|
101
|
+
),
|
|
102
|
+
model: str = typer.Option(None, "--model", "-m", help="The model to use for the chat service."),
|
|
103
|
+
response_format: ChatFormat = typer.Option(
|
|
104
|
+
ChatFormat.text, "--format", "-f", help="The format of the response."
|
|
105
|
+
),
|
|
106
|
+
stream: bool = typer.Option(False, "--stream", "-s", help="Stream the response."),
|
|
107
|
+
list_models: bool = typer.Option(
|
|
108
|
+
False, "--list-models", "-l", help="List available chat models."
|
|
109
|
+
),
|
|
110
|
+
):
|
|
111
|
+
"""
|
|
112
|
+
Interact with qBraid AI chat service.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
if list_models:
|
|
116
|
+
list_models_callback()
|
|
117
|
+
elif prompt:
|
|
118
|
+
prompt_callback(prompt, model, response_format, stream)
|
|
119
|
+
else:
|
|
120
|
+
raise click.UsageError(
|
|
121
|
+
"Invalid command. Please provide a prompt using --prompt "
|
|
122
|
+
"or use --list-models to view available models."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
82
126
|
if __name__ == "__main__":
|
|
83
127
|
app()
|
qbraid_cli/mcp/app.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Copyright (c) 2025, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
CLI commands for qBraid MCP aggregator.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from .serve import serve_mcp
|
|
11
|
+
|
|
12
|
+
mcp_app = typer.Typer(help="MCP (Model Context Protocol) aggregator commands")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@mcp_app.command("serve", help="Start MCP aggregator server for Claude Desktop")
|
|
16
|
+
def serve(
|
|
17
|
+
workspace: str = typer.Option(
|
|
18
|
+
"lab",
|
|
19
|
+
"--workspace",
|
|
20
|
+
"-w",
|
|
21
|
+
help="Workspace to connect to (lab, qbook, etc.)",
|
|
22
|
+
),
|
|
23
|
+
include_staging: bool = typer.Option(
|
|
24
|
+
False,
|
|
25
|
+
"--staging",
|
|
26
|
+
"-s",
|
|
27
|
+
help="Include staging endpoints",
|
|
28
|
+
),
|
|
29
|
+
debug: bool = typer.Option(
|
|
30
|
+
False,
|
|
31
|
+
"--debug",
|
|
32
|
+
"-d",
|
|
33
|
+
help="Enable debug logging",
|
|
34
|
+
),
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Start the qBraid MCP aggregator server.
|
|
38
|
+
|
|
39
|
+
This command starts a unified MCP server that connects to multiple qBraid MCP backends
|
|
40
|
+
(Lab pod_mcp, devices, jobs, etc.) and exposes them through a single stdio interface
|
|
41
|
+
for Claude Desktop and other AI agents.
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
# Start MCP server for Lab workspace
|
|
45
|
+
qbraid mcp serve
|
|
46
|
+
|
|
47
|
+
# Include staging endpoints for testing
|
|
48
|
+
qbraid mcp serve --staging
|
|
49
|
+
|
|
50
|
+
# Enable debug logging
|
|
51
|
+
qbraid mcp serve --debug
|
|
52
|
+
|
|
53
|
+
Claude Desktop Configuration:
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"qbraid": {
|
|
57
|
+
"command": "qbraid",
|
|
58
|
+
"args": ["mcp", "serve"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
"""
|
|
63
|
+
serve_mcp(workspace=workspace, include_staging=include_staging, debug=debug)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@mcp_app.command("status", help="Show status of MCP connections")
|
|
67
|
+
def status():
|
|
68
|
+
"""
|
|
69
|
+
Show the status of MCP backend connections.
|
|
70
|
+
|
|
71
|
+
Displays which backends are configured and their connection status.
|
|
72
|
+
"""
|
|
73
|
+
typer.echo("MCP Status:")
|
|
74
|
+
typer.echo(" Implementation in progress...")
|
|
75
|
+
typer.echo(" This will show connection status for all configured MCP backends")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@mcp_app.command("list", help="List available MCP servers")
|
|
79
|
+
def list_servers(
|
|
80
|
+
workspace: str = typer.Option(
|
|
81
|
+
None,
|
|
82
|
+
"--workspace",
|
|
83
|
+
"-w",
|
|
84
|
+
help="Filter by workspace (lab, qbook, etc.)",
|
|
85
|
+
),
|
|
86
|
+
include_staging: bool = typer.Option(
|
|
87
|
+
False,
|
|
88
|
+
"--staging",
|
|
89
|
+
"-s",
|
|
90
|
+
help="Include staging endpoints",
|
|
91
|
+
),
|
|
92
|
+
):
|
|
93
|
+
"""
|
|
94
|
+
List available qBraid MCP servers.
|
|
95
|
+
|
|
96
|
+
Shows all discovered MCP endpoints that can be connected to.
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
from qbraid_core.services.mcp import discover_mcp_servers
|
|
100
|
+
|
|
101
|
+
endpoints = discover_mcp_servers(
|
|
102
|
+
workspace=workspace or "lab", include_staging=include_staging
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if not endpoints:
|
|
106
|
+
typer.echo("No MCP servers found")
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
typer.echo(f"Found {len(endpoints)} MCP server(s):\n")
|
|
110
|
+
for endpoint in endpoints:
|
|
111
|
+
typer.echo(f" • {endpoint.name}")
|
|
112
|
+
typer.echo(f" {endpoint.base_url}")
|
|
113
|
+
if endpoint.description:
|
|
114
|
+
typer.echo(f" {endpoint.description}")
|
|
115
|
+
typer.echo()
|
|
116
|
+
|
|
117
|
+
except ImportError as exc:
|
|
118
|
+
typer.secho(
|
|
119
|
+
"Error: qbraid-core MCP module not found. "
|
|
120
|
+
"Please install qbraid-core with MCP support: pip install qbraid-core[mcp]",
|
|
121
|
+
fg=typer.colors.RED,
|
|
122
|
+
)
|
|
123
|
+
raise typer.Exit(1) from exc
|
|
124
|
+
except Exception as err:
|
|
125
|
+
typer.secho(f"Error listing MCP servers: {err}", fg=typer.colors.RED)
|
|
126
|
+
raise typer.Exit(1)
|