qbraid-cli 0.16.4__tar.gz → 0.16.6__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.
Files changed (71) hide show
  1. {qbraid_cli-0.16.4/qbraid_cli.egg-info → qbraid_cli-0.16.6}/PKG-INFO +4 -4
  2. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/pyproject.toml +4 -4
  3. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/agents/app.py +113 -40
  4. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6/qbraid_cli.egg-info}/PKG-INFO +4 -4
  5. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli.egg-info/requires.txt +3 -3
  6. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/LICENSE +0 -0
  7. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/README.md +0 -0
  8. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/__init__.py +0 -0
  9. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/_build_info.py +0 -0
  10. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/_version.py +0 -0
  11. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/account/__init__.py +0 -0
  12. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/account/app.py +0 -0
  13. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/admin/__init__.py +0 -0
  14. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/admin/app.py +0 -0
  15. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/admin/headers.py +0 -0
  16. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/admin/validation.py +0 -0
  17. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/agents/__init__.py +0 -0
  18. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/agents/permission_response.py +0 -0
  19. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/ai/__init__.py +0 -0
  20. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/ai/app.py +0 -0
  21. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/ai/token.py +0 -0
  22. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/comms/__init__.py +0 -0
  23. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/comms/app.py +0 -0
  24. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/compute/__init__.py +0 -0
  25. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/compute/app.py +0 -0
  26. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/compute/instances.py +0 -0
  27. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/compute/profiles.py +0 -0
  28. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/compute/server.py +0 -0
  29. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/compute/ssh.py +0 -0
  30. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/configure/__init__.py +0 -0
  31. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/configure/actions.py +0 -0
  32. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/configure/app.py +0 -0
  33. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/devices/__init__.py +0 -0
  34. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/devices/app.py +0 -0
  35. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/devices/validation.py +0 -0
  36. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/__init__.py +0 -0
  37. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/activate.py +0 -0
  38. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/app.py +0 -0
  39. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/create.py +0 -0
  40. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/data_handling.py +0 -0
  41. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/groups.py +0 -0
  42. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/publish.py +0 -0
  43. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/registry.py +0 -0
  44. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/envs/share.py +0 -0
  45. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/exceptions.py +0 -0
  46. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/handlers.py +0 -0
  47. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/jobs/__init__.py +0 -0
  48. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/jobs/app.py +0 -0
  49. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/jobs/groups.py +0 -0
  50. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/kernels/__init__.py +0 -0
  51. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/kernels/app.py +0 -0
  52. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/lab/__init__.py +0 -0
  53. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/lab/app.py +0 -0
  54. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/main.py +0 -0
  55. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/pip/__init__.py +0 -0
  56. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/pip/app.py +0 -0
  57. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/pip/hooks.py +0 -0
  58. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/projects/__init__.py +0 -0
  59. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/projects/app.py +0 -0
  60. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/py.typed +0 -0
  61. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/skills/__init__.py +0 -0
  62. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/skills/app.py +0 -0
  63. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/troubleshoot/__init__.py +0 -0
  64. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli/troubleshoot/app.py +0 -0
  65. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli.egg-info/SOURCES.txt +0 -0
  66. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli.egg-info/dependency_links.txt +0 -0
  67. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli.egg-info/entry_points.txt +0 -0
  68. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/qbraid_cli.egg-info/top_level.txt +0 -0
  69. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/setup.cfg +0 -0
  70. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/tests/test_cli_e2e.py +0 -0
  71. {qbraid_cli-0.16.4 → qbraid_cli-0.16.6}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qbraid-cli
3
- Version: 0.16.4
3
+ Version: 0.16.6
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.5.0
32
32
  Requires-Dist: questionary>=2.0.0
33
- Requires-Dist: qbraid-core>=0.6.11
33
+ Requires-Dist: qbraid-core>=0.6.15
34
34
  Provides-Extra: compute
35
- Requires-Dist: qbraid-core[compute]>=0.6.11; extra == "compute"
35
+ Requires-Dist: qbraid-core[compute]>=0.6.15; extra == "compute"
36
36
  Provides-Extra: envs
37
- Requires-Dist: qbraid-core[environments]>=0.6.11; extra == "envs"
37
+ Requires-Dist: qbraid-core[environments]>=0.6.15; 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.16.4"
7
+ version = "0.16.6"
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.5.0",
34
34
  "questionary>=2.0.0",
35
- "qbraid-core>=0.6.11",
35
+ "qbraid-core>=0.6.15",
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.6.11"]
47
- envs = ["qbraid-core[environments]>=0.6.11"]
46
+ compute = ["qbraid-core[compute]>=0.6.15"]
47
+ envs = ["qbraid-core[environments]>=0.6.15"]
48
48
  dev = ["isort", "black", "pytest", "pytest-cov", "pytest-asyncio"]
49
49
 
50
50
  [project.scripts]
@@ -985,6 +985,13 @@ def agents_kill(
985
985
  session = matches[0]
986
986
  pid = session.pid
987
987
 
988
+ if session.metadata.get("terminal_backend") == "jupyter":
989
+ from qbraid_core.services.agents import AgentLauncher
990
+
991
+ AgentLauncher().kill_bound(session.session_id)
992
+ console.print("[green]Stopped native Lab agent.[/green]")
993
+ return
994
+
988
995
  # For tmux-managed sessions, kill the tmux window
989
996
  if session.tmux_session and session.tmux_window:
990
997
  try:
@@ -1155,7 +1162,11 @@ def _interactive_attach(console_: Console) -> str:
1155
1162
  sessions = registry.get_active_sessions()
1156
1163
 
1157
1164
  # Only show sessions with tmux info (attachable)
1158
- tmux_sessions = [s for s in sessions if s.tmux_session and s.tmux_window]
1165
+ tmux_sessions = [
1166
+ s
1167
+ for s in sessions
1168
+ if (s.tmux_session and s.tmux_window) or s.metadata.get("terminal_backend") == "jupyter"
1169
+ ]
1159
1170
 
1160
1171
  if not tmux_sessions:
1161
1172
  non_tmux_count = len(sessions) - len(tmux_sessions)
@@ -1189,14 +1200,31 @@ def _interactive_attach(console_: Console) -> str:
1189
1200
 
1190
1201
 
1191
1202
  class ApprovalMode(str, Enum):
1192
- """How much the agent asks before acting.
1193
-
1194
- A launched agent is detached, so a confirmation prompt has nobody to
1195
- answer it — ``auto`` is the mode for anything you intend to leave running.
1196
- """
1203
+ """Explicit native presets and backwards-compatible ask/auto policies."""
1197
1204
 
1198
1205
  ASK = "ask"
1199
1206
  AUTO = "auto"
1207
+ MANUAL = "manual"
1208
+ AUTO_REVIEW = "auto-review"
1209
+ YOLO = "yolo"
1210
+
1211
+
1212
+ class TerminalBackend(str, Enum):
1213
+ """Terminal transport used to host an agent session."""
1214
+
1215
+ AUTO = "auto"
1216
+ JUPYTER = "jupyter"
1217
+ TMUX = "tmux"
1218
+
1219
+
1220
+ def _backend_launcher(backend):
1221
+ from qbraid_core.services.agents import AgentLauncher, AgentLauncherError
1222
+
1223
+ if backend == TerminalBackend.AUTO:
1224
+ return AgentLauncher()
1225
+ if "backend" not in inspect.signature(AgentLauncher).parameters:
1226
+ raise AgentLauncherError("Explicit terminal backends require a newer qbraid-core.")
1227
+ return AgentLauncher(backend=backend.value)
1200
1228
 
1201
1229
 
1202
1230
  class ExecutionMode(str, Enum):
@@ -1399,6 +1427,11 @@ def agents_settings(
1399
1427
 
1400
1428
  @agents_app.command("launch")
1401
1429
  def agents_launch(
1430
+ backend: TerminalBackend = typer.Option(
1431
+ TerminalBackend.AUTO,
1432
+ "--backend",
1433
+ help="auto: prefer the owning Lab terminal; jupyter: require Lab; tmux: run independently.",
1434
+ ),
1402
1435
  tool: Optional[str] = typer.Option(
1403
1436
  None, "--tool", "-t", help="Agent tool (claude, codeq, codex; claude-auto = claude --approval auto)"
1404
1437
  ),
@@ -1432,12 +1465,18 @@ def agents_launch(
1432
1465
  approval: Optional[ApprovalMode] = typer.Option(
1433
1466
  None,
1434
1467
  "--approval",
1435
- help="ask: the agent confirms before acting. auto: it acts without asking.",
1468
+ help=(
1469
+ "manual: the agent asks you. auto-review: the tool's own automatic reviewer "
1470
+ "decides. yolo: no permission checks. ask/auto: legacy modes, unchanged."
1471
+ ),
1472
+ ),
1473
+ yolo: bool = typer.Option(
1474
+ False, "--yolo", help="Claude: skip permissions. Codex: bypass approvals and its sandbox."
1436
1475
  ),
1437
1476
  ):
1438
1477
  """Launch an AI coding agent.
1439
1478
 
1440
- Starts the agent in a managed tmux session.
1479
+ Uses the owning Lab terminal when available; otherwise starts in tmux.
1441
1480
 
1442
1481
  Detached agents may wait for approval until you open their terminal or dashboard.
1443
1482
  Approval and execution mode are independent and remembered for resume.
@@ -1446,7 +1485,7 @@ def agents_launch(
1446
1485
  Choose it only where you trust the surrounding machine/container isolation.
1447
1486
  """
1448
1487
  try:
1449
- from qbraid_core.services.agents import AgentLauncher, AgentLauncherError
1488
+ from qbraid_core.services.agents import AgentLauncherError
1450
1489
 
1451
1490
  # Interactive mode if no tool specified
1452
1491
  if tool is None:
@@ -1459,13 +1498,29 @@ def agents_launch(
1459
1498
  tag = tag or result.get("tags")
1460
1499
  repo = repo or result.get("repo")
1461
1500
 
1501
+ if yolo:
1502
+ if tool not in ("claude", "claude-auto", "codex"):
1503
+ raise AgentLauncherError("--yolo is supported only for Claude and Codex.")
1504
+ # Only an explicit --approval yolo agrees with --yolo. "auto" used to
1505
+ # be accepted and silently upgraded, which for Codex drops the
1506
+ # workspace sandbox the user may have meant to keep.
1507
+ if approval not in (None, ApprovalMode.YOLO) or (
1508
+ execution_mode and execution_mode != ExecutionMode.CONTAINER
1509
+ ):
1510
+ raise AgentLauncherError("--yolo conflicts with the selected approval or execution mode.")
1511
+ approval = ApprovalMode.YOLO
1512
+ if tool == "codex":
1513
+ execution_mode = ExecutionMode.CONTAINER
1514
+
1462
1515
  if request_id is not None:
1463
1516
  import uuid
1464
1517
 
1465
1518
  if not compute or str(uuid.UUID(request_id)) != request_id:
1466
1519
  raise AgentLauncherError("--request-id requires --compute and a canonical UUID.")
1467
1520
 
1468
- launcher = AgentLauncher()
1521
+ if compute and backend != TerminalBackend.AUTO:
1522
+ raise AgentLauncherError("Remote launches select the backend on the destination; omit --backend.")
1523
+ launcher = _backend_launcher(backend)
1469
1524
 
1470
1525
  # Remote launch via SSH
1471
1526
  if compute:
@@ -1511,6 +1566,8 @@ def agents_launch(
1511
1566
  lines.append(f"[bold]Status:[/bold] [{style}]{status_val}[/{style}]")
1512
1567
  if session.compute_id:
1513
1568
  lines.append(f"[bold]Compute:[/bold] {session.compute_id}")
1569
+ if session.metadata.get("terminal_backend") == "jupyter":
1570
+ lines.append("[bold]Backend:[/bold] Jupyter (lifetime of the owning Lab server)")
1514
1571
  lines.append(f"[bold]Tmux Session:[/bold] {session.tmux_session or '-'}")
1515
1572
  lines.append(f"[bold]Tmux Window:[/bold] {session.tmux_window or '-'}")
1516
1573
  lines.append(f"[bold]PID:[/bold] {session.pid or '-'}")
@@ -1525,8 +1582,8 @@ def agents_launch(
1525
1582
  )
1526
1583
  console.print(panel)
1527
1584
  console.print()
1528
- console.print("[dim]Attach to the tmux session with:[/dim]")
1529
- console.print(f" tmux attach -t {session.tmux_session}")
1585
+ console.print("[dim]Open from the Agent Dashboard or attach with:[/dim]")
1586
+ console.print(f" qbraid agents attach {session.session_id}")
1530
1587
 
1531
1588
  except AgentLauncherError as e:
1532
1589
  console.print(
@@ -1614,6 +1671,11 @@ def _interactive_resume(console_: Console) -> str:
1614
1671
 
1615
1672
  @agents_app.command("resume")
1616
1673
  def agents_resume(
1674
+ backend: TerminalBackend = typer.Option(
1675
+ TerminalBackend.AUTO,
1676
+ "--backend",
1677
+ help="auto: prefer the owning Lab terminal; jupyter: require Lab; tmux: run independently.",
1678
+ ),
1617
1679
  session_id: Optional[str] = typer.Argument(
1618
1680
  None, help="Session ID, prefix, or name. Omit for interactive picker."
1619
1681
  ),
@@ -1621,17 +1683,22 @@ def agents_resume(
1621
1683
  execution_mode: Optional[ExecutionMode] = typer.Option(
1622
1684
  None, "--execution-mode", help="Override the recorded Codex execution mode for this resume."
1623
1685
  ),
1686
+ approval: Optional[ApprovalMode] = typer.Option(
1687
+ None, "--approval", help="Override recorded permissions for this resume. Omit to preserve them."
1688
+ ),
1624
1689
  compute: Optional[str] = typer.Option(None, "--compute", "-c", help="SSH alias for remote compute"),
1625
1690
  ):
1626
1691
  """Resume a previous agent session.
1627
1692
 
1628
- Resumes in a managed tmux window. With ``--compute`` the session is resumed
1693
+ Resumes with the selected terminal backend. With ``--compute`` the session is resumed
1629
1694
  on that machine, and a session id is required -- the interactive picker
1630
1695
  reads the local registry and cannot see another host's sessions.
1631
1696
  """
1632
1697
  try:
1633
- from qbraid_core.services.agents import AgentLauncher, AgentLauncherError
1698
+ from qbraid_core.services.agents import AgentLauncherError
1634
1699
 
1700
+ if compute and backend != TerminalBackend.AUTO:
1701
+ raise AgentLauncherError("Remote resumes select the backend on the destination; omit --backend.")
1635
1702
  if compute:
1636
1703
  if session_id is None:
1637
1704
  console.print(
@@ -1647,6 +1714,8 @@ def agents_resume(
1647
1714
  flags += f" --name {shlex.quote(name)}"
1648
1715
  if execution_mode:
1649
1716
  flags += f" --execution-mode {shlex.quote(execution_mode.value)}"
1717
+ if approval:
1718
+ flags += f" --approval {shlex.quote(approval.value)}"
1650
1719
  # markup=False -- see agents_status.
1651
1720
  console.print(
1652
1721
  _ssh_cmd(compute, f"qbraid agents resume {shlex.quote(session_id)}{flags}"),
@@ -1658,13 +1727,14 @@ def agents_resume(
1658
1727
  if session_id is None:
1659
1728
  session_id = _interactive_resume(console)
1660
1729
 
1661
- launcher = AgentLauncher()
1730
+ launcher = _backend_launcher(backend)
1662
1731
 
1663
1732
  # Try resume — handle active session and ambiguous matches
1664
1733
  try:
1665
1734
  session = launcher.resume(
1666
1735
  session_id=session_id,
1667
1736
  name=name,
1737
+ **(_approval_kwargs(launcher.resume, approval.value) if approval else {}),
1668
1738
  **_execution_kwargs(launcher.resume, execution_mode.value if execution_mode else None),
1669
1739
  )
1670
1740
  except AgentLauncherError as e:
@@ -1711,7 +1781,10 @@ def agents_resume(
1711
1781
  instructions=None,
1712
1782
  # A fork of a detached agent must not quietly
1713
1783
  # become one that waits for a human.
1714
- **_approval_kwargs(launcher.launch, getattr(original, "approval", None)),
1784
+ **_approval_kwargs(
1785
+ launcher.launch,
1786
+ approval.value if approval else getattr(original, "approval", None),
1787
+ ),
1715
1788
  **_execution_kwargs(
1716
1789
  launcher.launch,
1717
1790
  (
@@ -1782,6 +1855,7 @@ def agents_resume(
1782
1855
  session = launcher.resume(
1783
1856
  session_id=selected,
1784
1857
  name=name,
1858
+ **(_approval_kwargs(launcher.resume, approval.value) if approval else {}),
1785
1859
  **_execution_kwargs(launcher.resume, execution_mode.value if execution_mode else None),
1786
1860
  )
1787
1861
 
@@ -1792,6 +1866,8 @@ def agents_resume(
1792
1866
  lines.append(f"[bold]Session ID:[/bold] {session.session_id}")
1793
1867
  lines.append(f"[bold]Tool:[/bold] {session.tool.value if session.tool else '-'}")
1794
1868
  lines.append(f"[bold]Status:[/bold] [{style}]{status_val}[/{style}]")
1869
+ if session.metadata.get("terminal_backend") == "jupyter":
1870
+ lines.append("[bold]Backend:[/bold] Jupyter (lifetime of the owning Lab server)")
1795
1871
  lines.append(f"[bold]Tmux Session:[/bold] {session.tmux_session or '-'}")
1796
1872
  lines.append(f"[bold]Tmux Window:[/bold] {session.tmux_window or '-'}")
1797
1873
  lines.append(f"[bold]CWD:[/bold] {session.cwd or '-'}")
@@ -2485,29 +2561,14 @@ def agents_attach(
2485
2561
  ):
2486
2562
  """Attach to an agent's terminal.
2487
2563
 
2488
- Opens an interactive tmux window for the agent session.
2564
+ Reconnects to its recorded native terminal or tmux window.
2489
2565
  """
2490
2566
  try:
2491
- # Remote attach: SSH + tmux attach
2492
2567
  if compute:
2493
2568
  import shlex
2494
2569
 
2495
- if session_id:
2496
- # Attach to specific window
2497
- console.print(f"[dim]Attaching to {session_id} on {compute}... (Ctrl+b d to detach)[/dim]")
2498
- os.execvp(
2499
- "ssh",
2500
- [
2501
- "ssh",
2502
- "-t",
2503
- compute,
2504
- f"tmux attach -t qbraid-agents:{shlex.quote(session_id)}",
2505
- ],
2506
- )
2507
- else:
2508
- # Attach to the tmux session (user picks window)
2509
- console.print(f"[dim]Attaching to qbraid-agents on {compute}... (Ctrl+b d to detach)[/dim]")
2510
- os.execvp("ssh", ["ssh", "-t", compute, "tmux attach -t qbraid-agents"])
2570
+ command = "qbraid agents attach" + (" -- " + shlex.quote(session_id) if session_id else "")
2571
+ os.execvp("ssh", ["ssh", "-t", "--", compute, command])
2511
2572
  return
2512
2573
 
2513
2574
  # Interactive mode if no session_id provided
@@ -2522,6 +2583,23 @@ def agents_attach(
2522
2583
  console.print("[dim]Use 'qbraid agents list' to see active sessions.[/dim]")
2523
2584
  raise typer.Exit(code=1)
2524
2585
 
2586
+ if session.compute_id:
2587
+ import shlex
2588
+
2589
+ remote_id = session.metadata.get("remote_session_id") or session.session_id
2590
+ command = "qbraid agents attach -- " + shlex.quote(remote_id)
2591
+ os.execvp("ssh", ["ssh", "-t", "--", session.compute_id, command])
2592
+ return
2593
+ if session.machine_id:
2594
+ raise ValueError("Attach on the recorded machine; no SSH alias was recorded.")
2595
+
2596
+ if session.metadata.get("terminal_backend") == "jupyter":
2597
+ from qbraid_core.services.agents.jupyter_runtime import JupyterRuntimeClient
2598
+
2599
+ console.print("[dim]Attaching to native Lab terminal. Ctrl-] detaches.[/dim]")
2600
+ JupyterRuntimeClient(session.metadata["jupyter_server_id"]).attach(session)
2601
+ return
2602
+
2525
2603
  tmux_session = session.tmux_session
2526
2604
  tmux_window = session.tmux_window
2527
2605
 
@@ -2549,12 +2627,7 @@ def agents_attach(
2549
2627
 
2550
2628
  console.print(f"[dim]Attaching to {target}... (Ctrl+b d to detach)[/dim]")
2551
2629
 
2552
- # For remote sessions (has compute_id), SSH into the machine first
2553
- if session.compute_id:
2554
- alias = session.compute_id
2555
- os.execvp("ssh", ["ssh", "-t", alias, f"tmux attach -t {target}"])
2556
- else:
2557
- os.execvp("tmux", ["tmux", "attach", "-t", target])
2630
+ os.execvp("tmux", ["tmux", "attach", "-t", target])
2558
2631
 
2559
2632
  except (typer.Exit, SystemExit):
2560
2633
  raise
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qbraid-cli
3
- Version: 0.16.4
3
+ Version: 0.16.6
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.5.0
32
32
  Requires-Dist: questionary>=2.0.0
33
- Requires-Dist: qbraid-core>=0.6.11
33
+ Requires-Dist: qbraid-core>=0.6.15
34
34
  Provides-Extra: compute
35
- Requires-Dist: qbraid-core[compute]>=0.6.11; extra == "compute"
35
+ Requires-Dist: qbraid-core[compute]>=0.6.15; extra == "compute"
36
36
  Provides-Extra: envs
37
- Requires-Dist: qbraid-core[environments]>=0.6.11; extra == "envs"
37
+ Requires-Dist: qbraid-core[environments]>=0.6.15; 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.5.0
4
4
  questionary>=2.0.0
5
- qbraid-core>=0.6.11
5
+ qbraid-core>=0.6.15
6
6
 
7
7
  [compute]
8
- qbraid-core[compute]>=0.6.11
8
+ qbraid-core[compute]>=0.6.15
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.6.11
18
+ qbraid-core[environments]>=0.6.15
File without changes
File without changes
File without changes