qbraid-cli 0.16.2__tar.gz → 0.16.4__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.2/qbraid_cli.egg-info → qbraid_cli-0.16.4}/PKG-INFO +17 -4
  2. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/README.md +13 -0
  3. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/pyproject.toml +4 -4
  4. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/agents/app.py +78 -4
  5. qbraid_cli-0.16.4/qbraid_cli/comms/__init__.py +8 -0
  6. qbraid_cli-0.16.4/qbraid_cli/comms/app.py +124 -0
  7. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/main.py +2 -0
  8. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4/qbraid_cli.egg-info}/PKG-INFO +17 -4
  9. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli.egg-info/SOURCES.txt +2 -0
  10. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli.egg-info/requires.txt +3 -3
  11. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/LICENSE +0 -0
  12. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/__init__.py +0 -0
  13. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/_build_info.py +0 -0
  14. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/_version.py +0 -0
  15. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/account/__init__.py +0 -0
  16. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/account/app.py +0 -0
  17. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/admin/__init__.py +0 -0
  18. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/admin/app.py +0 -0
  19. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/admin/headers.py +0 -0
  20. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/admin/validation.py +0 -0
  21. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/agents/__init__.py +0 -0
  22. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/agents/permission_response.py +0 -0
  23. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/ai/__init__.py +0 -0
  24. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/ai/app.py +0 -0
  25. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/ai/token.py +0 -0
  26. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/compute/__init__.py +0 -0
  27. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/compute/app.py +0 -0
  28. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/compute/instances.py +0 -0
  29. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/compute/profiles.py +0 -0
  30. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/compute/server.py +0 -0
  31. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/compute/ssh.py +0 -0
  32. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/configure/__init__.py +0 -0
  33. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/configure/actions.py +0 -0
  34. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/configure/app.py +0 -0
  35. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/devices/__init__.py +0 -0
  36. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/devices/app.py +0 -0
  37. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/devices/validation.py +0 -0
  38. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/__init__.py +0 -0
  39. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/activate.py +0 -0
  40. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/app.py +0 -0
  41. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/create.py +0 -0
  42. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/data_handling.py +0 -0
  43. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/groups.py +0 -0
  44. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/publish.py +0 -0
  45. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/registry.py +0 -0
  46. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/envs/share.py +0 -0
  47. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/exceptions.py +0 -0
  48. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/handlers.py +0 -0
  49. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/jobs/__init__.py +0 -0
  50. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/jobs/app.py +0 -0
  51. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/jobs/groups.py +0 -0
  52. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/kernels/__init__.py +0 -0
  53. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/kernels/app.py +0 -0
  54. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/lab/__init__.py +0 -0
  55. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/lab/app.py +0 -0
  56. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/pip/__init__.py +0 -0
  57. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/pip/app.py +0 -0
  58. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/pip/hooks.py +0 -0
  59. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/projects/__init__.py +0 -0
  60. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/projects/app.py +0 -0
  61. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/py.typed +0 -0
  62. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/skills/__init__.py +0 -0
  63. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/skills/app.py +0 -0
  64. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/troubleshoot/__init__.py +0 -0
  65. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli/troubleshoot/app.py +0 -0
  66. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli.egg-info/dependency_links.txt +0 -0
  67. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli.egg-info/entry_points.txt +0 -0
  68. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/qbraid_cli.egg-info/top_level.txt +0 -0
  69. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/setup.cfg +0 -0
  70. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/tests/test_cli_e2e.py +0 -0
  71. {qbraid_cli-0.16.2 → qbraid_cli-0.16.4}/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.2
3
+ Version: 0.16.4
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.5
33
+ Requires-Dist: qbraid-core>=0.6.11
34
34
  Provides-Extra: compute
35
- Requires-Dist: qbraid-core[compute]>=0.6.5; extra == "compute"
35
+ Requires-Dist: qbraid-core[compute]>=0.6.11; extra == "compute"
36
36
  Provides-Extra: envs
37
- Requires-Dist: qbraid-core[environments]>=0.6.5; extra == "envs"
37
+ Requires-Dist: qbraid-core[environments]>=0.6.11; extra == "envs"
38
38
  Provides-Extra: dev
39
39
  Requires-Dist: isort; extra == "dev"
40
40
  Requires-Dist: black; extra == "dev"
@@ -161,3 +161,16 @@ Now you can continue to use the qBraid-CLI as normal from within your Jupyter no
161
161
  ```python
162
162
  In [2]: %qbraid jobs list
163
163
  ```
164
+
165
+ ### Agent updates
166
+
167
+ Send yourself Slack or email updates and read Slack replies using existing qBraid credentials:
168
+
169
+ ```bash
170
+ qbraid comms slack send "Tests passed" --json
171
+ qbraid comms email send --subject "Progress" --file report.txt --json
172
+ qbraid comms slack read --json
173
+ ```
174
+
175
+ See [communication commands](docs/comms-cli.md) for Slack connection, stdin input,
176
+ pagination, and deployment requirements.
@@ -116,3 +116,16 @@ Now you can continue to use the qBraid-CLI as normal from within your Jupyter no
116
116
  ```python
117
117
  In [2]: %qbraid jobs list
118
118
  ```
119
+
120
+ ### Agent updates
121
+
122
+ Send yourself Slack or email updates and read Slack replies using existing qBraid credentials:
123
+
124
+ ```bash
125
+ qbraid comms slack send "Tests passed" --json
126
+ qbraid comms email send --subject "Progress" --file report.txt --json
127
+ qbraid comms slack read --json
128
+ ```
129
+
130
+ See [communication commands](docs/comms-cli.md) for Slack connection, stdin input,
131
+ pagination, and deployment requirements.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qbraid-cli"
7
- version = "0.16.2"
7
+ version = "0.16.4"
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.5",
35
+ "qbraid-core>=0.6.11",
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.5"]
47
- envs = ["qbraid-core[environments]>=0.6.5"]
46
+ compute = ["qbraid-core[compute]>=0.6.11"]
47
+ envs = ["qbraid-core[environments]>=0.6.11"]
48
48
  dev = ["isort", "black", "pytest", "pytest-cov", "pytest-asyncio"]
49
49
 
50
50
  [project.scripts]
@@ -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:
@@ -361,10 +392,14 @@ def agents_list(
361
392
  # branch; the --json path above already bypasses Rich.
362
393
  console.print(result.stdout, markup=False)
363
394
  else:
395
+ reason = _remote_failure(result.stdout, result.stderr)
364
396
  if json_output:
365
- typer.echo(f"Failed: {result.stderr.strip()}", err=True)
366
- raise typer.Exit(code=1)
367
- 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)
368
403
  return
369
404
 
370
405
  registry = _get_registry()
@@ -1868,6 +1903,44 @@ def agents_read(
1868
1903
  # =============================================================================
1869
1904
 
1870
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
+
1871
1944
  @agents_app.command("approve")
1872
1945
  def agents_approve(
1873
1946
  session_id: str = typer.Argument(..., help="Session ID (prefix match)"),
@@ -2014,6 +2087,7 @@ def agents_watch(
2014
2087
  ["ssh", "-o", "ConnectTimeout=10", compute, remote_flags],
2015
2088
  stdout=_sp.PIPE,
2016
2089
  stderr=_sp.PIPE,
2090
+ stdin=_sp.DEVNULL,
2017
2091
  text=True,
2018
2092
  )
2019
2093
  try:
@@ -0,0 +1,8 @@
1
+ # Copyright (c) 2026, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """Communication with the authenticated account owner."""
5
+
6
+ from .app import comms_app
7
+
8
+ __all__ = ["comms_app"]
@@ -0,0 +1,124 @@
1
+ # Copyright (c) 2026, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """Agent-friendly Slack DMs and email updates through the qBraid API."""
5
+
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import typer
12
+
13
+ comms_app = typer.Typer(help="Send updates and read replies.", no_args_is_help=True)
14
+ slack_app = typer.Typer(help="Message your linked Slack account.", no_args_is_help=True)
15
+ email_app = typer.Typer(help="Email your qBraid account address.", no_args_is_help=True)
16
+ comms_app.add_typer(slack_app, name="slack")
17
+ comms_app.add_typer(email_app, name="email")
18
+
19
+
20
+ def _message(text: Optional[str], file: Optional[str]) -> str:
21
+ """Require one explicit source; never wait on stdin implicitly."""
22
+ if (text is None) == (file is None):
23
+ raise typer.BadParameter("Provide message text or --file PATH (use --file - for stdin).")
24
+ try:
25
+ if text is not None:
26
+ value = text
27
+ else:
28
+ assert file is not None # Exactly one source was validated above.
29
+ value = sys.stdin.read(20001) if file == "-" else Path(file).read_text(encoding="utf-8")
30
+ except (OSError, UnicodeError) as exc:
31
+ raise typer.BadParameter(f"Cannot read message: {exc}") from exc
32
+ return value
33
+
34
+
35
+ def _get_client():
36
+ """Load core lazily so older installations can still use other commands."""
37
+ try:
38
+ from qbraid_core.services.comms import CommsClient
39
+ except ImportError as exc:
40
+ raise RuntimeError(
41
+ "Communication commands require a qbraid-core release with CommsClient. "
42
+ "Upgrade qbraid-core to version 0.6.11 or newer."
43
+ ) from exc
44
+ return CommsClient()
45
+
46
+
47
+ def _run(method: str, *args, **kwargs):
48
+ """Translate core errors into CLI exit codes; core owns the API contract."""
49
+ try:
50
+ with _get_client() as client:
51
+ return getattr(client, method)(*args, **kwargs)
52
+ except ValueError as exc:
53
+ raise typer.BadParameter(str(exc)) from exc
54
+ except Exception as exc:
55
+ typer.echo(f"Error: {exc}", err=True)
56
+ if method in ("send_slack", "send_email"):
57
+ typer.echo(
58
+ "Delivery was not confirmed; check before retrying to avoid duplicate updates.", err=True
59
+ )
60
+ raise typer.Exit(1) from exc
61
+
62
+
63
+ def _sent(data, as_json: bool, label: str):
64
+ typer.echo(json.dumps(data, ensure_ascii=True) if as_json else label)
65
+
66
+
67
+ @slack_app.command("connect")
68
+ def slack_connect():
69
+ """Print the URL to link your Slack account."""
70
+ typer.echo(_run("get_slack_auth_url"))
71
+
72
+
73
+ @slack_app.command("send")
74
+ def slack_send(
75
+ message: Optional[str] = typer.Argument(None, help="Message for your linked Slack DM."),
76
+ file: Optional[str] = typer.Option(None, "--file", "-f", help="UTF-8 file, or - for stdin."),
77
+ as_json: bool = typer.Option(False, "--json", help="Output the API result as JSON."),
78
+ ):
79
+ """Send a Slack DM to yourself."""
80
+ text = _message(message, file)
81
+ data = _run("send_slack", text)
82
+ _sent(data, as_json, "Slack update sent.")
83
+
84
+
85
+ @email_app.command("send")
86
+ def email_send(
87
+ message: Optional[str] = typer.Argument(None, help="Plain-text email body."),
88
+ subject: str = typer.Option(..., "--subject", "-s", help="Single-line subject (max 200 characters)."),
89
+ file: Optional[str] = typer.Option(None, "--file", "-f", help="UTF-8 file, or - for stdin."),
90
+ as_json: bool = typer.Option(False, "--json", help="Output the API result as JSON."),
91
+ ):
92
+ """Email an update to yourself."""
93
+ text = _message(message, file)
94
+ data = _run("send_email", subject, text)
95
+ _sent(data, as_json, "Email update sent.")
96
+
97
+
98
+ @slack_app.command("read")
99
+ def slack_read(
100
+ after: Optional[str] = typer.Option(
101
+ None, "--after", help="Only messages newer than this Slack timestamp."
102
+ ),
103
+ cursor: Optional[str] = typer.Option(
104
+ None, "--cursor", help="Continue with nextCursor from a prior page."
105
+ ),
106
+ limit: int = typer.Option(
107
+ 15, "--limit", min=1, max=15, help="Slack history page size, before filtering."
108
+ ),
109
+ as_json: bool = typer.Option(False, "--json", help="Output messages and pagination as JSON."),
110
+ ):
111
+ """Read your messages to the Slack bot."""
112
+ data = _run("read_slack", after=after, cursor=cursor, limit=limit)
113
+ if as_json:
114
+ typer.echo(json.dumps(data, ensure_ascii=True))
115
+ return
116
+ for message in data["messages"]:
117
+ # Escape terminal control characters in remote message content.
118
+ typer.echo(f'{message["ts"]} {json.dumps(message["text"], ensure_ascii=True)}')
119
+ if not data["messages"]:
120
+ typer.echo("No messages from you on this page.")
121
+ if data.get("nextCursor"):
122
+ typer.echo(f'Next cursor: {data["nextCursor"]}')
123
+ elif data.get("hasMore"):
124
+ typer.echo("More history is available; the API did not return a continuation cursor.")
@@ -13,6 +13,7 @@ from qbraid_cli.account import account_app
13
13
  from qbraid_cli.admin import admin_app
14
14
  from qbraid_cli.agents import agents_app
15
15
  from qbraid_cli.ai.app import ai_app
16
+ from qbraid_cli.comms import comms_app
16
17
  from qbraid_cli.compute import compute_app
17
18
  from qbraid_cli.configure import configure_app
18
19
  from qbraid_cli.devices import devices_app
@@ -46,6 +47,7 @@ app.add_typer(admin_app, name="admin")
46
47
  app.add_typer(agents_app, name="agents")
47
48
  app.add_typer(ai_app, name="ai")
48
49
  app.add_typer(compute_app, name="compute")
50
+ app.add_typer(comms_app, name="comms")
49
51
  app.command(name="lab", context_settings=LAB_CONTEXT_SETTINGS)(lab_command)
50
52
  app.add_typer(configure_app, name="configure")
51
53
  app.add_typer(account_app, name="account")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qbraid-cli
3
- Version: 0.16.2
3
+ Version: 0.16.4
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.5
33
+ Requires-Dist: qbraid-core>=0.6.11
34
34
  Provides-Extra: compute
35
- Requires-Dist: qbraid-core[compute]>=0.6.5; extra == "compute"
35
+ Requires-Dist: qbraid-core[compute]>=0.6.11; extra == "compute"
36
36
  Provides-Extra: envs
37
- Requires-Dist: qbraid-core[environments]>=0.6.5; extra == "envs"
37
+ Requires-Dist: qbraid-core[environments]>=0.6.11; extra == "envs"
38
38
  Provides-Extra: dev
39
39
  Requires-Dist: isort; extra == "dev"
40
40
  Requires-Dist: black; extra == "dev"
@@ -161,3 +161,16 @@ Now you can continue to use the qBraid-CLI as normal from within your Jupyter no
161
161
  ```python
162
162
  In [2]: %qbraid jobs list
163
163
  ```
164
+
165
+ ### Agent updates
166
+
167
+ Send yourself Slack or email updates and read Slack replies using existing qBraid credentials:
168
+
169
+ ```bash
170
+ qbraid comms slack send "Tests passed" --json
171
+ qbraid comms email send --subject "Progress" --file report.txt --json
172
+ qbraid comms slack read --json
173
+ ```
174
+
175
+ See [communication commands](docs/comms-cli.md) for Slack connection, stdin input,
176
+ pagination, and deployment requirements.
@@ -26,6 +26,8 @@ qbraid_cli/agents/permission_response.py
26
26
  qbraid_cli/ai/__init__.py
27
27
  qbraid_cli/ai/app.py
28
28
  qbraid_cli/ai/token.py
29
+ qbraid_cli/comms/__init__.py
30
+ qbraid_cli/comms/app.py
29
31
  qbraid_cli/compute/__init__.py
30
32
  qbraid_cli/compute/app.py
31
33
  qbraid_cli/compute/instances.py
@@ -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.5
5
+ qbraid-core>=0.6.11
6
6
 
7
7
  [compute]
8
- qbraid-core[compute]>=0.6.5
8
+ qbraid-core[compute]>=0.6.11
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.5
18
+ qbraid-core[environments]>=0.6.11
File without changes
File without changes