qbraid-cli 0.16.1__tar.gz → 0.16.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.
Files changed (69) hide show
  1. {qbraid_cli-0.16.1/qbraid_cli.egg-info → qbraid_cli-0.16.3}/PKG-INFO +1 -1
  2. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/pyproject.toml +1 -1
  3. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/agents/app.py +157 -9
  4. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3/qbraid_cli.egg-info}/PKG-INFO +1 -1
  5. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/LICENSE +0 -0
  6. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/README.md +0 -0
  7. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/__init__.py +0 -0
  8. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/_build_info.py +0 -0
  9. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/_version.py +0 -0
  10. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/account/__init__.py +0 -0
  11. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/account/app.py +0 -0
  12. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/admin/__init__.py +0 -0
  13. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/admin/app.py +0 -0
  14. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/admin/headers.py +0 -0
  15. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/admin/validation.py +0 -0
  16. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/agents/__init__.py +0 -0
  17. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/agents/permission_response.py +0 -0
  18. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/ai/__init__.py +0 -0
  19. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/ai/app.py +0 -0
  20. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/ai/token.py +0 -0
  21. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/compute/__init__.py +0 -0
  22. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/compute/app.py +0 -0
  23. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/compute/instances.py +0 -0
  24. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/compute/profiles.py +0 -0
  25. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/compute/server.py +0 -0
  26. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/compute/ssh.py +0 -0
  27. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/configure/__init__.py +0 -0
  28. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/configure/actions.py +0 -0
  29. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/configure/app.py +0 -0
  30. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/devices/__init__.py +0 -0
  31. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/devices/app.py +0 -0
  32. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/devices/validation.py +0 -0
  33. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/__init__.py +0 -0
  34. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/activate.py +0 -0
  35. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/app.py +0 -0
  36. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/create.py +0 -0
  37. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/data_handling.py +0 -0
  38. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/groups.py +0 -0
  39. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/publish.py +0 -0
  40. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/registry.py +0 -0
  41. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/envs/share.py +0 -0
  42. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/exceptions.py +0 -0
  43. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/handlers.py +0 -0
  44. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/jobs/__init__.py +0 -0
  45. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/jobs/app.py +0 -0
  46. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/jobs/groups.py +0 -0
  47. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/kernels/__init__.py +0 -0
  48. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/kernels/app.py +0 -0
  49. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/lab/__init__.py +0 -0
  50. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/lab/app.py +0 -0
  51. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/main.py +0 -0
  52. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/pip/__init__.py +0 -0
  53. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/pip/app.py +0 -0
  54. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/pip/hooks.py +0 -0
  55. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/projects/__init__.py +0 -0
  56. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/projects/app.py +0 -0
  57. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/py.typed +0 -0
  58. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/skills/__init__.py +0 -0
  59. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/skills/app.py +0 -0
  60. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/troubleshoot/__init__.py +0 -0
  61. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli/troubleshoot/app.py +0 -0
  62. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli.egg-info/SOURCES.txt +0 -0
  63. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli.egg-info/dependency_links.txt +0 -0
  64. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli.egg-info/entry_points.txt +0 -0
  65. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli.egg-info/requires.txt +0 -0
  66. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/qbraid_cli.egg-info/top_level.txt +0 -0
  67. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/setup.cfg +0 -0
  68. {qbraid_cli-0.16.1 → qbraid_cli-0.16.3}/tests/test_cli_e2e.py +0 -0
  69. {qbraid_cli-0.16.1 → qbraid_cli-0.16.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.16.1
3
+ Version: 0.16.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qbraid-cli"
7
- version = "0.16.1"
7
+ version = "0.16.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" }]
@@ -89,13 +89,43 @@ def _ssh_cmd(ssh_alias: str, remote_cmd: str, timeout: int = 30) -> str:
89
89
  text=True,
90
90
  check=False,
91
91
  timeout=timeout,
92
+ # ssh reads stdin unless told otherwise, so inside a script or a
93
+ # `while read` loop it swallowed the caller's remaining input.
94
+ stdin=_sp.DEVNULL,
92
95
  )
93
96
  if result.returncode != 0:
94
- console.print(f"[red]Remote command failed: {result.stderr.strip()}[/red]")
97
+ console.print(
98
+ f"[red]Remote command failed: {_remote_failure(result.stdout, result.stderr)}[/red]",
99
+ markup=True,
100
+ )
95
101
  raise typer.Exit(code=1)
96
102
  return result.stdout
97
103
 
98
104
 
105
+ # ssh's own chatter, which says nothing about why the remote command failed.
106
+ _SSH_NOISE = ("Warning: Permanently added", "Connection to ", "Shared connection to ")
107
+
108
+
109
+ def _remote_failure(stdout: str, stderr: str) -> str:
110
+ """Say why a remote qbraid command failed.
111
+
112
+ The remote CLI prints its errors through Rich on *stdout*; stderr usually
113
+ holds only ssh's known-hosts warning. Reporting stderr alone turned
114
+ "Session not found" into "Warning: Permanently added ... to the list of
115
+ known hosts", which names the one thing that went right.
116
+ """
117
+ from rich.markup import escape
118
+
119
+ def useful(text: str) -> list[str]:
120
+ # The remote's Rich error panel arrives as box-drawing borders around
121
+ # the message; keep the message, drop the frame.
122
+ lines = [line.strip().strip("│╭╮╰╯─ ") for line in (text or "").splitlines()]
123
+ return [line for line in lines if line and not line.startswith(_SSH_NOISE)]
124
+
125
+ lines = useful(stdout)[-3:] or useful(stderr)[-3:]
126
+ return escape(" / ".join(lines)) if lines else "no output from the remote command"
127
+
128
+
99
129
  def _status_style(status_value: str) -> str:
100
130
  """Return a rich style string for a given agent status."""
101
131
  styles = {
@@ -339,6 +369,7 @@ def agents_list(
339
369
  text=True,
340
370
  check=False,
341
371
  timeout=30,
372
+ stdin=subprocess.DEVNULL,
342
373
  )
343
374
  if result.returncode == 0:
344
375
  if json_output:
@@ -357,12 +388,18 @@ def agents_list(
357
388
  raise typer.Exit(code=1)
358
389
  print(payload)
359
390
  else:
360
- console.print(result.stdout)
391
+ # markup=False -- see agents_status. Pre-existing on this
392
+ # branch; the --json path above already bypasses Rich.
393
+ console.print(result.stdout, markup=False)
361
394
  else:
395
+ reason = _remote_failure(result.stdout, result.stderr)
362
396
  if json_output:
363
- typer.echo(f"Failed: {result.stderr.strip()}", err=True)
364
- raise typer.Exit(code=1)
365
- console.print(f"[red]Failed: {result.stderr.strip()}[/red]")
397
+ typer.echo(f"Failed: {reason}", err=True)
398
+ else:
399
+ console.print(f"[red]Failed: {reason}[/red]")
400
+ # Both branches fail. The human-readable one used to print
401
+ # "Failed" and exit 0, so a script could not tell.
402
+ raise typer.Exit(code=1)
366
403
  return
367
404
 
368
405
  registry = _get_registry()
@@ -614,11 +651,28 @@ def agents_discover(
614
651
  @agents_app.command("status")
615
652
  def agents_status(
616
653
  session_id: str = typer.Argument(..., help="Session ID (full or prefix)"),
654
+ compute: Optional[str] = typer.Option(None, "--compute", "-c", help="SSH alias for remote compute"),
617
655
  ):
618
- """Show detailed status for an agent session."""
656
+ """Show detailed status for an agent session.
657
+
658
+ Without ``--compute`` this reads the local registry, which is per-host: an
659
+ agent on another machine is simply absent and reports as not found.
660
+ """
619
661
  try:
620
662
  from qbraid_core.services.agents import AgentSessionRegistryManager
621
663
 
664
+ if compute:
665
+ import shlex
666
+
667
+ # markup=False: remote output is already rendered, and Rich would
668
+ # read square brackets in it -- a branch name, a path -- as markup
669
+ # and silently drop them.
670
+ console.print(
671
+ _ssh_cmd(compute, f"qbraid agents status {shlex.quote(session_id)}"),
672
+ markup=False,
673
+ )
674
+ return
675
+
622
676
  registry = AgentSessionRegistryManager()
623
677
 
624
678
  # Support prefix and name matching
@@ -1567,14 +1621,39 @@ def agents_resume(
1567
1621
  execution_mode: Optional[ExecutionMode] = typer.Option(
1568
1622
  None, "--execution-mode", help="Override the recorded Codex execution mode for this resume."
1569
1623
  ),
1624
+ compute: Optional[str] = typer.Option(None, "--compute", "-c", help="SSH alias for remote compute"),
1570
1625
  ):
1571
1626
  """Resume a previous agent session.
1572
1627
 
1573
- Resumes in a managed tmux window.
1628
+ Resumes in a managed tmux window. With ``--compute`` the session is resumed
1629
+ on that machine, and a session id is required -- the interactive picker
1630
+ reads the local registry and cannot see another host's sessions.
1574
1631
  """
1575
1632
  try:
1576
1633
  from qbraid_core.services.agents import AgentLauncher, AgentLauncherError
1577
1634
 
1635
+ if compute:
1636
+ if session_id is None:
1637
+ console.print(
1638
+ "[red]--compute needs an explicit session id.[/red] The picker "
1639
+ "lists this host's sessions only; use 'qbraid agents list "
1640
+ f"--compute {compute}' to find one."
1641
+ )
1642
+ raise typer.Exit(code=2)
1643
+ import shlex
1644
+
1645
+ flags = ""
1646
+ if name:
1647
+ flags += f" --name {shlex.quote(name)}"
1648
+ if execution_mode:
1649
+ flags += f" --execution-mode {shlex.quote(execution_mode.value)}"
1650
+ # markup=False -- see agents_status.
1651
+ console.print(
1652
+ _ssh_cmd(compute, f"qbraid agents resume {shlex.quote(session_id)}{flags}"),
1653
+ markup=False,
1654
+ )
1655
+ return
1656
+
1578
1657
  # Interactive mode if no session_id provided
1579
1658
  if session_id is None:
1580
1659
  session_id = _interactive_resume(console)
@@ -1800,7 +1879,9 @@ def agents_read(
1800
1879
  import shlex
1801
1880
 
1802
1881
  output = _ssh_cmd(compute, f"qbraid agents read {shlex.quote(session_id)} --lines {lines}")
1803
- console.print(output)
1882
+ # markup=False -- see agents_status. Terminal output is exactly
1883
+ # where square brackets are most likely, and this was dropping them.
1884
+ console.print(output, markup=False)
1804
1885
  return
1805
1886
 
1806
1887
  launcher = AgentLauncher()
@@ -1822,17 +1903,66 @@ def agents_read(
1822
1903
  # =============================================================================
1823
1904
 
1824
1905
 
1906
+ @agents_app.command("notifier")
1907
+ def agents_notifier(
1908
+ interval: float = typer.Option(4.0, "--interval", "-i", min=1.0, help="Seconds between checks"),
1909
+ once: bool = typer.Option(False, "--once", help="Check once, print who was nudged, and exit"),
1910
+ ):
1911
+ """Wake idle agents when inbox mail arrives.
1912
+
1913
+ Inbox delivery is passive: an agent parked at its prompt never notices a
1914
+ message. This watches the inboxes of this machine's managed agents and
1915
+ types a one-line nudge into an idle agent's pane. Busy agents are left
1916
+ alone until they go idle, and a burst of messages is one nudge.
1917
+
1918
+ qBraid Lab runs this for you. Use it on a machine reached only over SSH.
1919
+ Same-host: it watches the inboxes on the machine it runs on.
1920
+ """
1921
+ try:
1922
+ try:
1923
+ from qbraid_core.services.agents.inbox_watcher import InboxWatcher, watch
1924
+ except ImportError:
1925
+ console.print("[red]This needs a newer qbraid-core.[/red] Run: pip install -U qbraid-core")
1926
+ raise typer.Exit(code=1) # pylint: disable=raise-missing-from
1927
+
1928
+ watcher = InboxWatcher()
1929
+ if once:
1930
+ nudged = watcher.poll_once()
1931
+ console.print(f"Nudged: {', '.join(nudged) if nudged else 'nobody'}", markup=False)
1932
+ return
1933
+ console.print(f"[dim]Watching inboxes every {interval:g}s... (Ctrl+C to stop)[/dim]")
1934
+ watch(interval=interval, watcher=watcher)
1935
+ except KeyboardInterrupt:
1936
+ console.print("[dim]Stopped.[/dim]")
1937
+ except (typer.Exit, SystemExit):
1938
+ raise
1939
+ except Exception as e: # pylint: disable=broad-exception-caught
1940
+ handle_error(message=str(e))
1941
+ raise typer.Exit(code=1)
1942
+
1943
+
1825
1944
  @agents_app.command("approve")
1826
1945
  def agents_approve(
1827
1946
  session_id: str = typer.Argument(..., help="Session ID (prefix match)"),
1947
+ compute: Optional[str] = typer.Option(None, "--compute", "-c", help="SSH alias for remote compute"),
1828
1948
  ):
1829
1949
  """Approve a tool-use prompt.
1830
1950
 
1831
- Sends 'y' + Enter to the agent's terminal.
1951
+ Sends 'y' + Enter to the agent's terminal. With ``--compute`` the approval
1952
+ is sent on that machine.
1832
1953
  """
1833
1954
  try:
1955
+ # Bound before the --compute branch below, which can raise: evaluating
1956
+ # the `except AgentLauncherError` clause needs the name.
1834
1957
  from qbraid_core.services.agents import AgentLauncher, AgentLauncherError
1835
1958
 
1959
+ if compute:
1960
+ import shlex
1961
+
1962
+ _ssh_cmd(compute, f"qbraid agents approve {shlex.quote(session_id)}")
1963
+ console.print(f"[green]Approval sent to session {session_id} on {compute}.[/green]")
1964
+ return
1965
+
1836
1966
  launcher = AgentLauncher()
1837
1967
  launcher.approve(session_id)
1838
1968
  console.print(f"[green]Approval sent to session {session_id}.[/green]")
@@ -1957,6 +2087,7 @@ def agents_watch(
1957
2087
  ["ssh", "-o", "ConnectTimeout=10", compute, remote_flags],
1958
2088
  stdout=_sp.PIPE,
1959
2089
  stderr=_sp.PIPE,
2090
+ stdin=_sp.DEVNULL,
1960
2091
  text=True,
1961
2092
  )
1962
2093
  try:
@@ -2837,6 +2968,11 @@ def agents_send_to(
2837
2968
 
2838
2969
  For multi-recipient / team broadcast, use ``qbraid agents broadcast``.
2839
2970
 
2971
+ Same-host only: an inbox is a JSONL file under ``~/.qbraid/inboxes/`` on
2972
+ the machine that owns it, so there is no ``--compute``. Cross-host
2973
+ messaging needs a transport, tracked in qBraid-CLI#328 / qbraid-core#307.
2974
+
2975
+
2840
2976
  Delivery is a JSONL append at ~/.qbraid/inboxes/<to-session-id>.jsonl
2841
2977
  (local host only — cross-host messaging isn't yet supported and will raise
2842
2978
  a clear error). Permission rules (leader/worker/observer/reviewer) are
@@ -2904,6 +3040,10 @@ def agents_broadcast(
2904
3040
  ) -> None:
2905
3041
  """Broadcast to multiple agent inboxes.
2906
3042
 
3043
+ Same-host only: an inbox is a JSONL file under ``~/.qbraid/inboxes/`` on
3044
+ the machine that owns it, so there is no ``--compute``. Cross-host
3045
+ messaging needs a transport, tracked in qBraid-CLI#328 / qbraid-core#307.
3046
+
2907
3047
  Two selectors — pick exactly one:
2908
3048
  * ``--team <team_id>`` — every session with that team_id (except sender)
2909
3049
  * ``--to A,B,C`` — explicit id/prefix/name list
@@ -3031,6 +3171,10 @@ def inbox_default( # pylint: disable=too-many-arguments
3031
3171
 
3032
3172
  Without a subcommand this reads the inbox (optionally streams with
3033
3173
  --follow). Use ``qbraid agents inbox mark-read`` to mark manually.
3174
+
3175
+ Same-host only: an inbox is a JSONL file under ``~/.qbraid/inboxes/`` on
3176
+ the machine that owns it, so there is no ``--compute``. Cross-host
3177
+ messaging needs a transport, tracked in qBraid-CLI#328 / qbraid-core#307.
3034
3178
  """
3035
3179
  if ctx.invoked_subcommand is not None:
3036
3180
  return
@@ -3138,6 +3282,10 @@ def agents_team(
3138
3282
  ) -> None:
3139
3283
  """Show a team roster with unread counts.
3140
3284
 
3285
+ Same-host only: an inbox is a JSONL file under ``~/.qbraid/inboxes/`` on
3286
+ the machine that owns it, so there is no ``--compute``. Cross-host
3287
+ messaging needs a transport, tracked in qBraid-CLI#328 / qbraid-core#307.
3288
+
3141
3289
  Answers the leader-orchestrator question "who is on my team, how many
3142
3290
  unread messages do they have, and what was the last thing they heard from
3143
3291
  anyone?" — which today requires peeking each worker's inbox individually.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qbraid-cli
3
- Version: 0.16.1
3
+ Version: 0.16.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
File without changes
File without changes
File without changes