qbraid-cli 0.15.1__tar.gz → 0.15.3__tar.gz
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-0.15.1/qbraid_cli.egg-info → qbraid_cli-0.15.3}/PKG-INFO +4 -4
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/pyproject.toml +4 -4
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/agents/app.py +278 -32
- qbraid_cli-0.15.3/qbraid_cli/agents/permission_response.py +49 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/skills/app.py +49 -5
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3/qbraid_cli.egg-info}/PKG-INFO +4 -4
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli.egg-info/SOURCES.txt +1 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli.egg-info/requires.txt +3 -3
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/LICENSE +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/README.md +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/_build_info.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/_version.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/account/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/account/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/admin/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/admin/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/admin/headers.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/admin/validation.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/agents/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/ai/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/ai/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/ai/token.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/compute/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/compute/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/compute/instances.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/compute/profiles.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/compute/server.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/compute/ssh.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/configure/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/configure/actions.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/configure/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/devices/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/devices/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/devices/validation.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/activate.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/create.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/data_handling.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/groups.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/publish.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/registry.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/envs/share.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/exceptions.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/handlers.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/jobs/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/jobs/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/jobs/groups.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/kernels/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/kernels/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/lab/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/lab/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/main.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/pip/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/pip/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/pip/hooks.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/projects/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/projects/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/py.typed +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/skills/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/troubleshoot/__init__.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli/troubleshoot/app.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli.egg-info/dependency_links.txt +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli.egg-info/entry_points.txt +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/qbraid_cli.egg-info/top_level.txt +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/setup.cfg +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/tests/test_cli_e2e.py +0 -0
- {qbraid_cli-0.15.1 → qbraid_cli-0.15.3}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qbraid-cli
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.3
|
|
4
4
|
Summary: Command Line Interface for interacting with all parts of the qBraid platform.
|
|
5
5
|
Author-email: qBraid Development Team <contact@qbraid.com>
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -30,11 +30,11 @@ Requires-Dist: typer>=0.12.1
|
|
|
30
30
|
Requires-Dist: rich>=10.11.0
|
|
31
31
|
Requires-Dist: click<=8.3.3
|
|
32
32
|
Requires-Dist: questionary>=2.0.0
|
|
33
|
-
Requires-Dist: qbraid-core>=0.5
|
|
33
|
+
Requires-Dist: qbraid-core>=0.6.5
|
|
34
34
|
Provides-Extra: compute
|
|
35
|
-
Requires-Dist: qbraid-core[compute]>=0.5
|
|
35
|
+
Requires-Dist: qbraid-core[compute]>=0.6.5; extra == "compute"
|
|
36
36
|
Provides-Extra: envs
|
|
37
|
-
Requires-Dist: qbraid-core[environments]>=0.5
|
|
37
|
+
Requires-Dist: qbraid-core[environments]>=0.6.5; extra == "envs"
|
|
38
38
|
Provides-Extra: dev
|
|
39
39
|
Requires-Dist: isort; extra == "dev"
|
|
40
40
|
Requires-Dist: black; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "qbraid-cli"
|
|
7
|
-
version = "0.15.
|
|
7
|
+
version = "0.15.3"
|
|
8
8
|
description = "Command Line Interface for interacting with all parts of the qBraid platform."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "qBraid Development Team", email = "contact@qbraid.com" }]
|
|
@@ -32,7 +32,7 @@ dependencies = [
|
|
|
32
32
|
"rich>=10.11.0",
|
|
33
33
|
"click<=8.3.3",
|
|
34
34
|
"questionary>=2.0.0",
|
|
35
|
-
"qbraid-core>=0.5
|
|
35
|
+
"qbraid-core>=0.6.5",
|
|
36
36
|
]
|
|
37
37
|
requires-python = ">= 3.10"
|
|
38
38
|
|
|
@@ -43,8 +43,8 @@ Documentation = "https://docs.qbraid.com/v2/cli/api-reference/qbraid"
|
|
|
43
43
|
Discord = "https://discord.gg/KugF6Cnncm"
|
|
44
44
|
|
|
45
45
|
[project.optional-dependencies]
|
|
46
|
-
compute = ["qbraid-core[compute]>=0.5
|
|
47
|
-
envs = ["qbraid-core[environments]>=0.5
|
|
46
|
+
compute = ["qbraid-core[compute]>=0.6.5"]
|
|
47
|
+
envs = ["qbraid-core[environments]>=0.6.5"]
|
|
48
48
|
dev = ["isort", "black", "pytest", "pytest-cov", "pytest-asyncio"]
|
|
49
49
|
|
|
50
50
|
[project.scripts]
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"""Agents CLI - Monitor and manage AI coding agent sessions."""
|
|
5
5
|
|
|
6
6
|
import inspect
|
|
7
|
+
import json
|
|
7
8
|
import os
|
|
8
9
|
import re
|
|
9
10
|
import signal
|
|
@@ -207,7 +208,6 @@ def agents_register():
|
|
|
207
208
|
|
|
208
209
|
Reads session data from stdin JSON (used by hooks).
|
|
209
210
|
"""
|
|
210
|
-
import json
|
|
211
211
|
import sys
|
|
212
212
|
|
|
213
213
|
try:
|
|
@@ -237,7 +237,6 @@ def agents_deregister():
|
|
|
237
237
|
|
|
238
238
|
Reads session data from stdin JSON (used by hooks).
|
|
239
239
|
"""
|
|
240
|
-
import json
|
|
241
240
|
import sys
|
|
242
241
|
|
|
243
242
|
try:
|
|
@@ -381,10 +380,9 @@ def agents_list(
|
|
|
381
380
|
|
|
382
381
|
# JSON output mode
|
|
383
382
|
if json_output:
|
|
384
|
-
import json as _json
|
|
385
383
|
|
|
386
384
|
output = [s.model_dump(mode="json", by_alias=True, exclude_none=True) for s in sessions]
|
|
387
|
-
print(
|
|
385
|
+
print(json.dumps(output, indent=2))
|
|
388
386
|
return
|
|
389
387
|
|
|
390
388
|
# Sort
|
|
@@ -1109,6 +1107,57 @@ class ApprovalMode(str, Enum):
|
|
|
1109
1107
|
AUTO = "auto"
|
|
1110
1108
|
|
|
1111
1109
|
|
|
1110
|
+
class ExecutionMode(str, Enum):
|
|
1111
|
+
"""Codex command isolation, independent of approval prompts."""
|
|
1112
|
+
|
|
1113
|
+
NATIVE = "native"
|
|
1114
|
+
WORKSPACE = "workspace"
|
|
1115
|
+
CONTAINER = "container"
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
def _execution_kwargs(method: Callable[..., Any], mode: Optional[str]) -> Dict[str, Any]:
|
|
1119
|
+
"""Never silently discard an explicitly selected execution policy."""
|
|
1120
|
+
if mode is None:
|
|
1121
|
+
return {}
|
|
1122
|
+
try:
|
|
1123
|
+
params = inspect.signature(method).parameters
|
|
1124
|
+
supported = "execution_mode" in params or any(
|
|
1125
|
+
param.kind is inspect.Parameter.VAR_KEYWORD for param in params.values()
|
|
1126
|
+
)
|
|
1127
|
+
except (TypeError, ValueError):
|
|
1128
|
+
supported = False
|
|
1129
|
+
if not supported:
|
|
1130
|
+
handle_error(
|
|
1131
|
+
error_type="UnsupportedOptionError",
|
|
1132
|
+
message="--execution-mode requires a newer qbraid-core. Upgrade qbraid-core first.",
|
|
1133
|
+
include_traceback=False,
|
|
1134
|
+
)
|
|
1135
|
+
return {"execution_mode": mode}
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
def _request_id_kwargs(method: Callable[..., Any], request_id: Optional[str]) -> Dict[str, Any]:
|
|
1139
|
+
"""Require explicit Core support before promising remote replay protection."""
|
|
1140
|
+
if request_id is None:
|
|
1141
|
+
return {}
|
|
1142
|
+
try:
|
|
1143
|
+
params = inspect.signature(method).parameters
|
|
1144
|
+
supported = "request_id" in params or any(
|
|
1145
|
+
param.kind is inspect.Parameter.VAR_KEYWORD for param in params.values()
|
|
1146
|
+
)
|
|
1147
|
+
except (TypeError, ValueError):
|
|
1148
|
+
supported = False
|
|
1149
|
+
if not supported:
|
|
1150
|
+
handle_error(
|
|
1151
|
+
error_type="UnsupportedOptionError",
|
|
1152
|
+
message=(
|
|
1153
|
+
"--request-id requires a newer qbraid-core. "
|
|
1154
|
+
"Upgrade Core on this machine and the destination before launching."
|
|
1155
|
+
),
|
|
1156
|
+
include_traceback=False,
|
|
1157
|
+
)
|
|
1158
|
+
return {"request_id": request_id}
|
|
1159
|
+
|
|
1160
|
+
|
|
1112
1161
|
def _approval_kwargs(method: Callable[..., Any], approval: Optional[str]) -> Dict[str, Any]:
|
|
1113
1162
|
"""Forward ``approval`` only to a qbraid-core new enough to accept it.
|
|
1114
1163
|
|
|
@@ -1147,6 +1196,115 @@ def _approval_kwargs(method: Callable[..., Any], approval: Optional[str]) -> Dic
|
|
|
1147
1196
|
return {}
|
|
1148
1197
|
|
|
1149
1198
|
|
|
1199
|
+
@agents_app.command("readiness")
|
|
1200
|
+
def agents_readiness(
|
|
1201
|
+
tool: str = typer.Option(..., "--tool", help="Provider to inspect: claude, codex, or codeq."),
|
|
1202
|
+
cwd: Optional[str] = typer.Option(None, "--cwd", help="Working directory on this machine."),
|
|
1203
|
+
approval: Optional[ApprovalMode] = typer.Option(None, "--approval"),
|
|
1204
|
+
execution_mode: Optional[ExecutionMode] = typer.Option(None, "--execution-mode"),
|
|
1205
|
+
json_output: bool = typer.Option(False, "--json", help="Print structured checks and recovery actions."),
|
|
1206
|
+
verify_auth: bool = typer.Option(
|
|
1207
|
+
False,
|
|
1208
|
+
"--verify-auth",
|
|
1209
|
+
help="Explicitly check provider authentication; does not sign in or transfer credentials.",
|
|
1210
|
+
),
|
|
1211
|
+
):
|
|
1212
|
+
"""Check this destination before launch.
|
|
1213
|
+
|
|
1214
|
+
Known blockers exit with status 1.
|
|
1215
|
+
"""
|
|
1216
|
+
try:
|
|
1217
|
+
from qbraid_core.services.agents.readiness import agent_readiness
|
|
1218
|
+
except ImportError:
|
|
1219
|
+
typer.echo("Destination readiness requires a newer qbraid-core.", err=True)
|
|
1220
|
+
raise typer.Exit(2) from None
|
|
1221
|
+
report = agent_readiness(
|
|
1222
|
+
tool,
|
|
1223
|
+
cwd=cwd,
|
|
1224
|
+
approval=approval.value if approval else None,
|
|
1225
|
+
execution_mode=execution_mode.value if execution_mode else None,
|
|
1226
|
+
verify_auth=verify_auth,
|
|
1227
|
+
)
|
|
1228
|
+
if json_output:
|
|
1229
|
+
print(json.dumps(report))
|
|
1230
|
+
else:
|
|
1231
|
+
for check in report["checks"]:
|
|
1232
|
+
console.print(f"{check['id']}: {check['state']} — {check['message']}", markup=False)
|
|
1233
|
+
for action in report.get("auth", {}).get("actions", []):
|
|
1234
|
+
if action.get("command"):
|
|
1235
|
+
console.print(str(action["command"]), markup=False)
|
|
1236
|
+
console.print("Unknown checks are not proof of readiness. Use --json for recovery actions.")
|
|
1237
|
+
if not report["ready"]:
|
|
1238
|
+
raise typer.Exit(1)
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
@agents_app.command("machine")
|
|
1242
|
+
def agents_machine(
|
|
1243
|
+
serve: bool = typer.Option(
|
|
1244
|
+
False, "--serve", help="Serve JSON lines over authenticated SSH stdin/stdout."
|
|
1245
|
+
),
|
|
1246
|
+
once: bool = typer.Option(
|
|
1247
|
+
False, "--once", help="Read one JSON request from stdin and return one response."
|
|
1248
|
+
),
|
|
1249
|
+
):
|
|
1250
|
+
"""Serve the fleet machine protocol.
|
|
1251
|
+
|
|
1252
|
+
Protocol v1 writes only JSON frames to stdout.
|
|
1253
|
+
"""
|
|
1254
|
+
if serve == once:
|
|
1255
|
+
raise typer.BadParameter("Choose exactly one of --serve or --once.")
|
|
1256
|
+
try:
|
|
1257
|
+
from qbraid_core.services.agents.machine_protocol import serve_machine
|
|
1258
|
+
except ImportError:
|
|
1259
|
+
typer.echo("Machine protocol requires a newer qbraid-core.", err=True)
|
|
1260
|
+
raise typer.Exit(2) from None
|
|
1261
|
+
serve_machine(once=once)
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
@agents_app.command("settings")
|
|
1265
|
+
def agents_settings(
|
|
1266
|
+
execution_mode: Optional[ExecutionMode] = typer.Option(
|
|
1267
|
+
None, "--execution-mode", help="Save the Codex execution default for this machine."
|
|
1268
|
+
),
|
|
1269
|
+
reset: bool = typer.Option(False, "--reset", help="Remove this machine's execution preference."),
|
|
1270
|
+
json_output: bool = typer.Option(
|
|
1271
|
+
False, "--json", help="Print structured settings and sandbox readiness."
|
|
1272
|
+
),
|
|
1273
|
+
):
|
|
1274
|
+
"""Inspect or configure agent execution.
|
|
1275
|
+
|
|
1276
|
+
Defaults are shared by the CLI and dashboard on this machine.
|
|
1277
|
+
"""
|
|
1278
|
+
try:
|
|
1279
|
+
from qbraid_core.services.agents.execution import execution_settings, set_execution_default
|
|
1280
|
+
|
|
1281
|
+
if reset and execution_mode is not None:
|
|
1282
|
+
raise ValueError("Choose --reset or --execution-mode, not both.")
|
|
1283
|
+
if reset:
|
|
1284
|
+
settings = set_execution_default(None)
|
|
1285
|
+
elif execution_mode is not None:
|
|
1286
|
+
settings = set_execution_default(execution_mode.value)
|
|
1287
|
+
else:
|
|
1288
|
+
settings = execution_settings()
|
|
1289
|
+
if json_output:
|
|
1290
|
+
print(json.dumps(settings))
|
|
1291
|
+
else:
|
|
1292
|
+
console.print(
|
|
1293
|
+
"Codex execution default: " + (settings["execution_mode"] or "not set (legacy defaults)")
|
|
1294
|
+
)
|
|
1295
|
+
console.print("Scope: this machine; applies to new CLI and dashboard launches.")
|
|
1296
|
+
console.print(settings["workspace"]["detail"])
|
|
1297
|
+
console.print("Override one launch with --execution-mode native|workspace|container.")
|
|
1298
|
+
except ImportError:
|
|
1299
|
+
handle_error(
|
|
1300
|
+
error_type="UnsupportedOptionError",
|
|
1301
|
+
message="Agent execution settings require a newer qbraid-core.",
|
|
1302
|
+
include_traceback=False,
|
|
1303
|
+
)
|
|
1304
|
+
except Exception as error:
|
|
1305
|
+
handle_error(error_type=type(error).__name__, message=str(error), include_traceback=False)
|
|
1306
|
+
|
|
1307
|
+
|
|
1150
1308
|
@agents_app.command("launch")
|
|
1151
1309
|
def agents_launch(
|
|
1152
1310
|
tool: Optional[str] = typer.Option(
|
|
@@ -1167,6 +1325,18 @@ def agents_launch(
|
|
|
1167
1325
|
compute: Optional[str] = typer.Option(
|
|
1168
1326
|
None, "--compute", "-c", help="SSH alias for remote compute server"
|
|
1169
1327
|
),
|
|
1328
|
+
request_id: Optional[str] = typer.Option(
|
|
1329
|
+
None, "--request-id", help="Remote launch UUID retained for reconciling an interrupted request."
|
|
1330
|
+
),
|
|
1331
|
+
execution_mode: Optional[ExecutionMode] = typer.Option(
|
|
1332
|
+
None,
|
|
1333
|
+
"--execution-mode",
|
|
1334
|
+
help=(
|
|
1335
|
+
"Codex only: native uses Codex defaults; workspace adds its workspace sandbox; "
|
|
1336
|
+
"container uses current user access without an extra Codex sandbox (no sudo). "
|
|
1337
|
+
"Omitted preserves existing behavior. Independent of --approval."
|
|
1338
|
+
),
|
|
1339
|
+
),
|
|
1170
1340
|
approval: Optional[ApprovalMode] = typer.Option(
|
|
1171
1341
|
None,
|
|
1172
1342
|
"--approval",
|
|
@@ -1177,11 +1347,11 @@ def agents_launch(
|
|
|
1177
1347
|
|
|
1178
1348
|
Starts the agent in a managed tmux session.
|
|
1179
1349
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1350
|
+
Detached agents may wait for approval until you open their terminal or dashboard.
|
|
1351
|
+
Approval and execution mode are independent and remembered for resume.
|
|
1352
|
+
--approval auto disables approval prompts; --execution-mode container
|
|
1353
|
+
explicitly uses the current user's access without an extra Codex sandbox.
|
|
1354
|
+
Choose it only where you trust the surrounding machine/container isolation.
|
|
1185
1355
|
"""
|
|
1186
1356
|
try:
|
|
1187
1357
|
from qbraid_core.services.agents import AgentLauncher, AgentLauncherError
|
|
@@ -1197,6 +1367,12 @@ def agents_launch(
|
|
|
1197
1367
|
tag = tag or result.get("tags")
|
|
1198
1368
|
repo = repo or result.get("repo")
|
|
1199
1369
|
|
|
1370
|
+
if request_id is not None:
|
|
1371
|
+
import uuid
|
|
1372
|
+
|
|
1373
|
+
if not compute or str(uuid.UUID(request_id)) != request_id:
|
|
1374
|
+
raise AgentLauncherError("--request-id requires --compute and a canonical UUID.")
|
|
1375
|
+
|
|
1200
1376
|
launcher = AgentLauncher()
|
|
1201
1377
|
|
|
1202
1378
|
# Remote launch via SSH
|
|
@@ -1210,7 +1386,9 @@ def agents_launch(
|
|
|
1210
1386
|
instructions=instructions,
|
|
1211
1387
|
agent_type=agent_type,
|
|
1212
1388
|
tags=tag,
|
|
1389
|
+
**_request_id_kwargs(launcher.remote_launch, request_id),
|
|
1213
1390
|
**_approval_kwargs(launcher.remote_launch, approval.value if approval else None),
|
|
1391
|
+
**_execution_kwargs(launcher.remote_launch, execution_mode.value if execution_mode else None),
|
|
1214
1392
|
)
|
|
1215
1393
|
else:
|
|
1216
1394
|
|
|
@@ -1228,6 +1406,7 @@ def agents_launch(
|
|
|
1228
1406
|
team_id=team,
|
|
1229
1407
|
tags=tag,
|
|
1230
1408
|
**_approval_kwargs(launcher.launch, approval.value if approval else None),
|
|
1409
|
+
**_execution_kwargs(launcher.launch, execution_mode.value if execution_mode else None),
|
|
1231
1410
|
progress_callback=_clone_progress if repo else None,
|
|
1232
1411
|
)
|
|
1233
1412
|
|
|
@@ -1287,7 +1466,9 @@ def _interactive_resume(console_: Console) -> str:
|
|
|
1287
1466
|
import questionary
|
|
1288
1467
|
|
|
1289
1468
|
registry = _get_registry()
|
|
1290
|
-
|
|
1469
|
+
# Use the display enumeration so retained transcript aliases do not appear
|
|
1470
|
+
# as duplicate choices. Stopped sessions remain available for resuming.
|
|
1471
|
+
all_sessions = registry.search_sessions(include_stopped=True)
|
|
1291
1472
|
|
|
1292
1473
|
# Show sessions that could be resumed (have a session_id, prefer recent)
|
|
1293
1474
|
resumable = sorted(
|
|
@@ -1345,6 +1526,9 @@ def agents_resume(
|
|
|
1345
1526
|
None, help="Session ID, prefix, or name. Omit for interactive picker."
|
|
1346
1527
|
),
|
|
1347
1528
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Agent name for the resumed session"),
|
|
1529
|
+
execution_mode: Optional[ExecutionMode] = typer.Option(
|
|
1530
|
+
None, "--execution-mode", help="Override the recorded Codex execution mode for this resume."
|
|
1531
|
+
),
|
|
1348
1532
|
):
|
|
1349
1533
|
"""Resume a previous agent session.
|
|
1350
1534
|
|
|
@@ -1361,7 +1545,11 @@ def agents_resume(
|
|
|
1361
1545
|
|
|
1362
1546
|
# Try resume — handle active session and ambiguous matches
|
|
1363
1547
|
try:
|
|
1364
|
-
session = launcher.resume(
|
|
1548
|
+
session = launcher.resume(
|
|
1549
|
+
session_id=session_id,
|
|
1550
|
+
name=name,
|
|
1551
|
+
**_execution_kwargs(launcher.resume, execution_mode.value if execution_mode else None),
|
|
1552
|
+
)
|
|
1365
1553
|
except AgentLauncherError as e:
|
|
1366
1554
|
err_msg = str(e)
|
|
1367
1555
|
|
|
@@ -1407,6 +1595,14 @@ def agents_resume(
|
|
|
1407
1595
|
# A fork of a detached agent must not quietly
|
|
1408
1596
|
# become one that waits for a human.
|
|
1409
1597
|
**_approval_kwargs(launcher.launch, getattr(original, "approval", None)),
|
|
1598
|
+
**_execution_kwargs(
|
|
1599
|
+
launcher.launch,
|
|
1600
|
+
(
|
|
1601
|
+
execution_mode.value
|
|
1602
|
+
if execution_mode
|
|
1603
|
+
else getattr(original, "execution_mode", None)
|
|
1604
|
+
),
|
|
1605
|
+
),
|
|
1410
1606
|
)
|
|
1411
1607
|
else:
|
|
1412
1608
|
console.print("[red]Could not find original session to fork.[/red]")
|
|
@@ -1425,7 +1621,7 @@ def agents_resume(
|
|
|
1425
1621
|
import questionary
|
|
1426
1622
|
|
|
1427
1623
|
registry = _get_registry()
|
|
1428
|
-
all_sessions = registry.
|
|
1624
|
+
all_sessions = registry.search_sessions(include_stopped=True)
|
|
1429
1625
|
matches = [
|
|
1430
1626
|
s
|
|
1431
1627
|
for s in all_sessions
|
|
@@ -1438,6 +1634,13 @@ def agents_resume(
|
|
|
1438
1634
|
]
|
|
1439
1635
|
matches.sort(key=lambda s: s.last_activity or s.started_at or 0, reverse=True)
|
|
1440
1636
|
|
|
1637
|
+
if not matches:
|
|
1638
|
+
console.print(
|
|
1639
|
+
"[yellow]No visible sessions match this prefix. "
|
|
1640
|
+
"Use a full session ID to resume a retained record.[/yellow]"
|
|
1641
|
+
)
|
|
1642
|
+
raise typer.Exit(code=1)
|
|
1643
|
+
|
|
1441
1644
|
choices = []
|
|
1442
1645
|
for s in matches:
|
|
1443
1646
|
sid = s.session_id[:8]
|
|
@@ -1459,7 +1662,11 @@ def agents_resume(
|
|
|
1459
1662
|
if selected is None:
|
|
1460
1663
|
raise typer.Exit()
|
|
1461
1664
|
|
|
1462
|
-
session = launcher.resume(
|
|
1665
|
+
session = launcher.resume(
|
|
1666
|
+
session_id=selected,
|
|
1667
|
+
name=name,
|
|
1668
|
+
**_execution_kwargs(launcher.resume, execution_mode.value if execution_mode else None),
|
|
1669
|
+
)
|
|
1463
1670
|
|
|
1464
1671
|
status_val = session.status.value if session.status else "idle"
|
|
1465
1672
|
style = _status_style(status_val)
|
|
@@ -1718,9 +1925,8 @@ def agents_watch(
|
|
|
1718
1925
|
else:
|
|
1719
1926
|
# Parse and render
|
|
1720
1927
|
try:
|
|
1721
|
-
import json as _json
|
|
1722
1928
|
|
|
1723
|
-
event =
|
|
1929
|
+
event = json.loads(line)
|
|
1724
1930
|
entry_type = event.get("type", "unknown")
|
|
1725
1931
|
content = event.get("content", "")
|
|
1726
1932
|
ts = event.get("timestamp")
|
|
@@ -1780,6 +1986,11 @@ def agents_watch(
|
|
|
1780
1986
|
can_prompt = interactive and not stream
|
|
1781
1987
|
can_notify = stream
|
|
1782
1988
|
|
|
1989
|
+
if can_prompt or can_notify:
|
|
1990
|
+
from .permission_response import require_bound_permission_support
|
|
1991
|
+
|
|
1992
|
+
require_bound_permission_support()
|
|
1993
|
+
|
|
1783
1994
|
# Resolve the session ID used for permission files
|
|
1784
1995
|
perm_session_id = session.session_id
|
|
1785
1996
|
|
|
@@ -1840,9 +2051,17 @@ def agents_watch(
|
|
|
1840
2051
|
# (stdin can't be read from multiple threads safely).
|
|
1841
2052
|
import threading
|
|
1842
2053
|
|
|
1843
|
-
from qbraid_core.services.agents.permissions import get_pending_request
|
|
2054
|
+
from qbraid_core.services.agents.permissions import get_pending_request
|
|
2055
|
+
|
|
2056
|
+
from .permission_response import (
|
|
2057
|
+
UnsupportedPermissionApproval,
|
|
2058
|
+
request_identity,
|
|
2059
|
+
respond_to_displayed_request,
|
|
2060
|
+
supports_bound_approval,
|
|
2061
|
+
)
|
|
1844
2062
|
|
|
1845
2063
|
perm_stop_event = threading.Event()
|
|
2064
|
+
perm_retry = threading.Event()
|
|
1846
2065
|
perm_pending = threading.Event() # Set when a permission request is detected
|
|
1847
2066
|
perm_data: dict = {} # Stores the current request
|
|
1848
2067
|
|
|
@@ -1850,8 +2069,12 @@ def agents_watch(
|
|
|
1850
2069
|
|
|
1851
2070
|
def _permission_detector():
|
|
1852
2071
|
"""Background thread that detects permission requests."""
|
|
2072
|
+
last_request_id = None
|
|
1853
2073
|
while not perm_stop_event.is_set():
|
|
1854
2074
|
if not perm_pending.is_set():
|
|
2075
|
+
if perm_retry.is_set():
|
|
2076
|
+
last_request_id = None
|
|
2077
|
+
perm_retry.clear()
|
|
1855
2078
|
request = None
|
|
1856
2079
|
|
|
1857
2080
|
if oc_port:
|
|
@@ -1877,15 +2100,21 @@ def agents_watch(
|
|
|
1877
2100
|
# Claude/Codex: check file-based permission request
|
|
1878
2101
|
request = get_pending_request(perm_session_id)
|
|
1879
2102
|
|
|
1880
|
-
if request:
|
|
2103
|
+
if not isinstance(request, dict):
|
|
2104
|
+
request = None
|
|
2105
|
+
identity = request_identity(perm_session_id, request) if request else None
|
|
2106
|
+
if request and identity != last_request_id:
|
|
2107
|
+
last_request_id = identity
|
|
2108
|
+
perm_data.clear()
|
|
1881
2109
|
perm_data.update(request)
|
|
1882
2110
|
perm_pending.set()
|
|
1883
2111
|
|
|
1884
2112
|
if can_notify:
|
|
1885
|
-
import json as _json
|
|
1886
2113
|
|
|
1887
2114
|
notification = {
|
|
1888
2115
|
"type": "permission_required",
|
|
2116
|
+
"request_id": identity,
|
|
2117
|
+
"protocol_version": request.get("protocol_version", 1),
|
|
1889
2118
|
"session_id": perm_session_id,
|
|
1890
2119
|
"tool_name": request.get("tool_name", "?"),
|
|
1891
2120
|
"summary": request.get("summary", ""),
|
|
@@ -1894,7 +2123,11 @@ def agents_watch(
|
|
|
1894
2123
|
if request.get("opencode_request_id"):
|
|
1895
2124
|
notification["opencode_request_id"] = request["opencode_request_id"]
|
|
1896
2125
|
notification["opencode_port"] = oc_port
|
|
1897
|
-
print(
|
|
2126
|
+
print(json.dumps(notification), flush=True)
|
|
2127
|
+
|
|
2128
|
+
if not can_prompt:
|
|
2129
|
+
perm_data.clear()
|
|
2130
|
+
perm_pending.clear()
|
|
1898
2131
|
|
|
1899
2132
|
# Wait for main loop to clear the flag
|
|
1900
2133
|
while perm_pending.is_set() and not perm_stop_event.is_set():
|
|
@@ -1937,7 +2170,13 @@ def agents_watch(
|
|
|
1937
2170
|
console.print()
|
|
1938
2171
|
console.print(f"[bold yellow]Permission required:[/bold yellow] [bold]{tool_name}[/bold]")
|
|
1939
2172
|
console.print(f"[dim]{summary}[/dim]")
|
|
1940
|
-
|
|
2173
|
+
if supports_bound_approval(perm_data):
|
|
2174
|
+
console.print(" [bold]1.[/bold] Allow")
|
|
2175
|
+
else:
|
|
2176
|
+
console.print(
|
|
2177
|
+
"[dim]Automatic Allow unavailable for this legacy gate; "
|
|
2178
|
+
"restart the agent or use its terminal.[/dim]"
|
|
2179
|
+
)
|
|
1941
2180
|
console.print(" [bold]2.[/bold] Deny")
|
|
1942
2181
|
console.print(" [bold]3.[/bold] Pass to agent (show Claude's prompt)")
|
|
1943
2182
|
|
|
@@ -1953,8 +2192,8 @@ def agents_watch(
|
|
|
1953
2192
|
oc_reply_map = {"allow": "once", "deny": "reject", "ask": "once"}
|
|
1954
2193
|
reply_to_permission(oc_req_port, oc_req_id, oc_reply_map.get(decision, "once"))
|
|
1955
2194
|
else:
|
|
1956
|
-
#
|
|
1957
|
-
|
|
2195
|
+
# Bind the reply to the request actually shown to the user.
|
|
2196
|
+
respond_to_displayed_request(perm_session_id, perm_data, decision, reason)
|
|
1958
2197
|
|
|
1959
2198
|
try:
|
|
1960
2199
|
choice = console.input("[dim]> [/dim]").strip()
|
|
@@ -1971,16 +2210,21 @@ def agents_watch(
|
|
|
1971
2210
|
_send_response("ask")
|
|
1972
2211
|
console.print("[dim]Invalid — passed to agent's terminal.[/dim]")
|
|
1973
2212
|
except (KeyboardInterrupt, EOFError):
|
|
1974
|
-
|
|
2213
|
+
try:
|
|
2214
|
+
_send_response("ask")
|
|
2215
|
+
except ValueError as exc:
|
|
2216
|
+
perm_retry.set()
|
|
2217
|
+
console.print(f"[yellow]{exc}[/yellow]")
|
|
2218
|
+
except UnsupportedPermissionApproval as exc:
|
|
2219
|
+
console.print(f"[yellow]{exc}[/yellow]")
|
|
2220
|
+
except ValueError as exc:
|
|
2221
|
+
perm_retry.set()
|
|
2222
|
+
console.print(f"[yellow]{exc}[/yellow]")
|
|
1975
2223
|
|
|
1976
|
-
perm_pending.clear()
|
|
1977
2224
|
perm_data.clear()
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
Path(os.path.expanduser("~/.qbraid/permissions")) / f"{perm_session_id}.request.json"
|
|
1982
|
-
)
|
|
1983
|
-
req_file.unlink(missing_ok=True)
|
|
2225
|
+
perm_pending.clear()
|
|
2226
|
+
# Only the tool-side gate owns request-file cleanup. Removing it
|
|
2227
|
+
# here can erase the next request after the tool consumes our reply.
|
|
1984
2228
|
continue
|
|
1985
2229
|
|
|
1986
2230
|
# Drain available transcript entries
|
|
@@ -2010,7 +2254,6 @@ def agents_watch(
|
|
|
2010
2254
|
tool_info = f" ({entry.tool_name})"
|
|
2011
2255
|
|
|
2012
2256
|
if stream:
|
|
2013
|
-
import json as _json
|
|
2014
2257
|
|
|
2015
2258
|
event = {
|
|
2016
2259
|
"type": entry_type,
|
|
@@ -2021,7 +2264,7 @@ def agents_watch(
|
|
|
2021
2264
|
"cost_usd": entry.cost_usd,
|
|
2022
2265
|
"token_count": entry.token_count,
|
|
2023
2266
|
}
|
|
2024
|
-
print(
|
|
2267
|
+
print(json.dumps({k: v for k, v in event.items() if v is not None}), flush=True)
|
|
2025
2268
|
else:
|
|
2026
2269
|
line = f"{prefix}{type_label}{tool_info}: [{style}]{content}[/{style}]"
|
|
2027
2270
|
console.print(line, highlight=False)
|
|
@@ -2030,6 +2273,9 @@ def agents_watch(
|
|
|
2030
2273
|
console.print("\n[dim]Stopped watching.[/dim]")
|
|
2031
2274
|
except (typer.Exit, SystemExit):
|
|
2032
2275
|
raise
|
|
2276
|
+
except ValueError as e:
|
|
2277
|
+
handle_error(message=str(e), include_traceback=False)
|
|
2278
|
+
raise typer.Exit(code=1)
|
|
2033
2279
|
except Exception as e:
|
|
2034
2280
|
handle_error(message=str(e))
|
|
2035
2281
|
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright (c) 2026, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""Bind watch-mode decisions to the permission request displayed to the user."""
|
|
5
|
+
|
|
6
|
+
from qbraid_core.services.agents import permissions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UnsupportedPermissionApproval(ValueError):
|
|
10
|
+
"""The displayed gate cannot safely accept an automatic approval."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def supports_bound_approval(request: dict) -> bool:
|
|
14
|
+
"""Only explicit v2-or-newer gates can bind automatic approvals."""
|
|
15
|
+
try:
|
|
16
|
+
return int(request.get("protocol_version", 1)) >= 2
|
|
17
|
+
except (TypeError, ValueError, OverflowError):
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def require_bound_permission_support() -> None:
|
|
22
|
+
"""Refuse permission handling on core versions that ignore request identity."""
|
|
23
|
+
if not getattr(permissions, "SUPPORTS_BOUND_PERMISSION_RESPONSES", False):
|
|
24
|
+
raise ValueError(
|
|
25
|
+
"Permission handling requires a qbraid-core release with bound-response support; "
|
|
26
|
+
"upgrade core first, or watch read-only with --no-interactive."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def request_identity(session_id: str, request: dict) -> str:
|
|
31
|
+
"""Support nonce requests and the timestamp identity of older integrations."""
|
|
32
|
+
return (
|
|
33
|
+
request.get("request_id")
|
|
34
|
+
or request.get("opencode_request_id")
|
|
35
|
+
or (f"{session_id}:{request['timestamp']}" if request.get("timestamp") is not None else session_id)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def respond_to_displayed_request(session_id: str, request: dict, decision: str, reason: str = "") -> None:
|
|
40
|
+
"""Reject an expired prompt without reporting success or deleting gate state."""
|
|
41
|
+
require_bound_permission_support()
|
|
42
|
+
if decision == "allow" and not supports_bound_approval(request):
|
|
43
|
+
raise UnsupportedPermissionApproval(
|
|
44
|
+
"This legacy gate cannot bind approvals. Restart the agent or use its terminal."
|
|
45
|
+
)
|
|
46
|
+
if not permissions.respond_to_permission(
|
|
47
|
+
session_id, request_identity(session_id, request), decision, reason
|
|
48
|
+
):
|
|
49
|
+
raise ValueError("Permission request expired or was already answered")
|
|
@@ -271,7 +271,7 @@ def skills_install(
|
|
|
271
271
|
|
|
272
272
|
try:
|
|
273
273
|
console.print(f"[dim]Installing skill '{slug}'...[/dim]")
|
|
274
|
-
results = run_progress_task(install_skill)
|
|
274
|
+
results = run_progress_task(install_skill, include_error_traceback=False)
|
|
275
275
|
|
|
276
276
|
if legacy_core and force:
|
|
277
277
|
console.print(
|
|
@@ -329,6 +329,11 @@ def skills_uninstall(
|
|
|
329
329
|
"-t",
|
|
330
330
|
help="Target AI coding tool(s) to uninstall from",
|
|
331
331
|
),
|
|
332
|
+
path: Optional[str] = typer.Option(
|
|
333
|
+
None,
|
|
334
|
+
"--path",
|
|
335
|
+
help="Uninstall from this directory instead of the tool directories",
|
|
336
|
+
),
|
|
332
337
|
) -> None:
|
|
333
338
|
"""Uninstall a skill from AI coding tools.
|
|
334
339
|
|
|
@@ -337,9 +342,15 @@ def skills_uninstall(
|
|
|
337
342
|
"""
|
|
338
343
|
|
|
339
344
|
def uninstall_skill():
|
|
345
|
+
from pathlib import Path as _Path
|
|
346
|
+
|
|
340
347
|
from qbraid_core.services.skills import SkillsClient
|
|
341
348
|
|
|
342
349
|
client = SkillsClient()
|
|
350
|
+
if path:
|
|
351
|
+
# `install --path` had no counterpart, so such an install could
|
|
352
|
+
# only be removed with `rm -rf`.
|
|
353
|
+
return client.uninstall_skill(slug, install_path=_Path(path).expanduser())
|
|
343
354
|
return client.uninstall_skill(slug, target=target.value)
|
|
344
355
|
|
|
345
356
|
console = Console()
|
|
@@ -359,6 +370,8 @@ def skills_uninstall(
|
|
|
359
370
|
else:
|
|
360
371
|
console.print(f"[yellow]Skill '{slug}' was not installed in any location.[/yellow]")
|
|
361
372
|
|
|
373
|
+
except typer.Exit:
|
|
374
|
+
raise
|
|
362
375
|
except Exception as err: # pylint: disable=broad-exception-caught
|
|
363
376
|
handle_error(message=f"Failed to uninstall skill: {err}")
|
|
364
377
|
|
|
@@ -371,23 +384,39 @@ def skills_installed(
|
|
|
371
384
|
"-t",
|
|
372
385
|
help="Show skills for specific tool only",
|
|
373
386
|
),
|
|
387
|
+
path: Optional[str] = typer.Option(
|
|
388
|
+
None,
|
|
389
|
+
"--path",
|
|
390
|
+
help="List skills installed in this directory",
|
|
391
|
+
),
|
|
374
392
|
) -> None:
|
|
375
393
|
"""List locally installed skills.
|
|
376
394
|
|
|
377
395
|
Shows each skill's name, registry slug and version per tool.
|
|
378
396
|
"""
|
|
397
|
+
# `all` is in the enum for `install`/`uninstall`, where it means every
|
|
398
|
+
# tool. There is no directory called "all", so passing it here reached
|
|
399
|
+
# get_skills_install_path and raised.
|
|
400
|
+
one_target = target if target and target is not SkillTargetOption.ALL else None
|
|
379
401
|
|
|
380
402
|
def list_installed():
|
|
403
|
+
from pathlib import Path as _Path
|
|
404
|
+
|
|
381
405
|
from qbraid_core.services.skills import SkillsClient
|
|
382
406
|
|
|
383
407
|
client = SkillsClient()
|
|
408
|
+
install_path = _Path(path).expanduser() if path else None
|
|
384
409
|
if hasattr(client, "list_all_installed_skills_detailed"):
|
|
385
|
-
if
|
|
386
|
-
return {
|
|
410
|
+
if install_path is not None:
|
|
411
|
+
return {"custom": client.list_installed_skills_detailed(install_path=install_path)}
|
|
412
|
+
if one_target:
|
|
413
|
+
return {one_target.value: client.list_installed_skills_detailed(target=one_target.value)}
|
|
387
414
|
return client.list_all_installed_skills_detailed()
|
|
388
415
|
# older qbraid-core: directory names only
|
|
389
|
-
if
|
|
390
|
-
listed = {
|
|
416
|
+
if install_path is not None:
|
|
417
|
+
listed = {"custom": client.list_installed_skills(install_path=install_path)}
|
|
418
|
+
elif one_target:
|
|
419
|
+
listed = {one_target.value: client.list_installed_skills(target=one_target.value)}
|
|
391
420
|
else:
|
|
392
421
|
listed = client.list_all_installed_skills()
|
|
393
422
|
return {
|
|
@@ -424,6 +453,10 @@ def skills_installed(
|
|
|
424
453
|
|
|
425
454
|
console.print("")
|
|
426
455
|
|
|
456
|
+
except typer.Exit:
|
|
457
|
+
# handle_error already reported and chose the exit code; catching it
|
|
458
|
+
# again below printed a second, empty "Error:" block.
|
|
459
|
+
raise
|
|
427
460
|
except Exception as err: # pylint: disable=broad-exception-caught
|
|
428
461
|
handle_error(message=f"Failed to list installed skills: {err}")
|
|
429
462
|
|
|
@@ -431,13 +464,22 @@ def skills_installed(
|
|
|
431
464
|
@skills_app.command(name="check")
|
|
432
465
|
def skills_check(
|
|
433
466
|
slug: str = typer.Argument(..., help="The skill slug to check"),
|
|
467
|
+
path: Optional[str] = typer.Option(
|
|
468
|
+
None,
|
|
469
|
+
"--path",
|
|
470
|
+
help="Check this directory instead of the tool directories",
|
|
471
|
+
),
|
|
434
472
|
) -> None:
|
|
435
473
|
"""Check if a skill is installed and where."""
|
|
436
474
|
|
|
437
475
|
def check_skill():
|
|
476
|
+
from pathlib import Path as _Path
|
|
477
|
+
|
|
438
478
|
from qbraid_core.services.skills import SkillsClient
|
|
439
479
|
|
|
440
480
|
client = SkillsClient()
|
|
481
|
+
if path:
|
|
482
|
+
return {"custom": client.is_skill_installed(slug, install_path=_Path(path).expanduser())}
|
|
441
483
|
return client.is_skill_installed_anywhere(slug)
|
|
442
484
|
|
|
443
485
|
console = Console()
|
|
@@ -458,6 +500,8 @@ def skills_check(
|
|
|
458
500
|
console.print(f"\n[yellow]Skill '{slug}' is not installed anywhere.[/yellow]")
|
|
459
501
|
console.print(f"\n[dim]Install it with: qbraid skills install {slug}[/dim]")
|
|
460
502
|
|
|
503
|
+
except typer.Exit:
|
|
504
|
+
raise
|
|
461
505
|
except Exception as err: # pylint: disable=broad-exception-caught
|
|
462
506
|
handle_error(message=f"Failed to check skill: {err}")
|
|
463
507
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qbraid-cli
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.3
|
|
4
4
|
Summary: Command Line Interface for interacting with all parts of the qBraid platform.
|
|
5
5
|
Author-email: qBraid Development Team <contact@qbraid.com>
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -30,11 +30,11 @@ Requires-Dist: typer>=0.12.1
|
|
|
30
30
|
Requires-Dist: rich>=10.11.0
|
|
31
31
|
Requires-Dist: click<=8.3.3
|
|
32
32
|
Requires-Dist: questionary>=2.0.0
|
|
33
|
-
Requires-Dist: qbraid-core>=0.5
|
|
33
|
+
Requires-Dist: qbraid-core>=0.6.5
|
|
34
34
|
Provides-Extra: compute
|
|
35
|
-
Requires-Dist: qbraid-core[compute]>=0.5
|
|
35
|
+
Requires-Dist: qbraid-core[compute]>=0.6.5; extra == "compute"
|
|
36
36
|
Provides-Extra: envs
|
|
37
|
-
Requires-Dist: qbraid-core[environments]>=0.5
|
|
37
|
+
Requires-Dist: qbraid-core[environments]>=0.6.5; extra == "envs"
|
|
38
38
|
Provides-Extra: dev
|
|
39
39
|
Requires-Dist: isort; extra == "dev"
|
|
40
40
|
Requires-Dist: black; extra == "dev"
|
|
@@ -2,10 +2,10 @@ typer>=0.12.1
|
|
|
2
2
|
rich>=10.11.0
|
|
3
3
|
click<=8.3.3
|
|
4
4
|
questionary>=2.0.0
|
|
5
|
-
qbraid-core>=0.5
|
|
5
|
+
qbraid-core>=0.6.5
|
|
6
6
|
|
|
7
7
|
[compute]
|
|
8
|
-
qbraid-core[compute]>=0.5
|
|
8
|
+
qbraid-core[compute]>=0.6.5
|
|
9
9
|
|
|
10
10
|
[dev]
|
|
11
11
|
isort
|
|
@@ -15,4 +15,4 @@ pytest-cov
|
|
|
15
15
|
pytest-asyncio
|
|
16
16
|
|
|
17
17
|
[envs]
|
|
18
|
-
qbraid-core[environments]>=0.5
|
|
18
|
+
qbraid-core[environments]>=0.6.5
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|