qbraid-cli 0.16.10__tar.gz → 0.16.11__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 (72) hide show
  1. {qbraid_cli-0.16.10/qbraid_cli.egg-info → qbraid_cli-0.16.11}/PKG-INFO +4 -4
  2. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/pyproject.toml +4 -4
  3. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/agents/app.py +4 -0
  4. qbraid_cli-0.16.11/qbraid_cli/agents/work.py +188 -0
  5. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11/qbraid_cli.egg-info}/PKG-INFO +4 -4
  6. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli.egg-info/SOURCES.txt +1 -0
  7. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli.egg-info/requires.txt +3 -3
  8. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/LICENSE +0 -0
  9. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/README.md +0 -0
  10. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/__init__.py +0 -0
  11. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/_build_info.py +0 -0
  12. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/_version.py +0 -0
  13. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/account/__init__.py +0 -0
  14. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/account/app.py +0 -0
  15. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/admin/__init__.py +0 -0
  16. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/admin/app.py +0 -0
  17. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/admin/headers.py +0 -0
  18. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/admin/validation.py +0 -0
  19. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/agents/__init__.py +0 -0
  20. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/agents/permission_response.py +0 -0
  21. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/ai/__init__.py +0 -0
  22. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/ai/app.py +0 -0
  23. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/ai/token.py +0 -0
  24. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/comms/__init__.py +0 -0
  25. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/comms/app.py +0 -0
  26. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/compute/__init__.py +0 -0
  27. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/compute/app.py +0 -0
  28. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/compute/instances.py +0 -0
  29. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/compute/profiles.py +0 -0
  30. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/compute/server.py +0 -0
  31. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/compute/ssh.py +0 -0
  32. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/configure/__init__.py +0 -0
  33. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/configure/actions.py +0 -0
  34. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/configure/app.py +0 -0
  35. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/devices/__init__.py +0 -0
  36. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/devices/app.py +0 -0
  37. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/devices/validation.py +0 -0
  38. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/__init__.py +0 -0
  39. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/activate.py +0 -0
  40. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/app.py +0 -0
  41. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/create.py +0 -0
  42. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/data_handling.py +0 -0
  43. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/groups.py +0 -0
  44. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/publish.py +0 -0
  45. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/registry.py +0 -0
  46. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/envs/share.py +0 -0
  47. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/exceptions.py +0 -0
  48. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/handlers.py +0 -0
  49. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/jobs/__init__.py +0 -0
  50. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/jobs/app.py +0 -0
  51. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/jobs/groups.py +0 -0
  52. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/kernels/__init__.py +0 -0
  53. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/kernels/app.py +0 -0
  54. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/lab/__init__.py +0 -0
  55. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/lab/app.py +0 -0
  56. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/main.py +0 -0
  57. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/pip/__init__.py +0 -0
  58. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/pip/app.py +0 -0
  59. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/pip/hooks.py +0 -0
  60. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/projects/__init__.py +0 -0
  61. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/projects/app.py +0 -0
  62. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/py.typed +0 -0
  63. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/skills/__init__.py +0 -0
  64. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/skills/app.py +0 -0
  65. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/troubleshoot/__init__.py +0 -0
  66. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli/troubleshoot/app.py +0 -0
  67. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli.egg-info/dependency_links.txt +0 -0
  68. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli.egg-info/entry_points.txt +0 -0
  69. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/qbraid_cli.egg-info/top_level.txt +0 -0
  70. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/setup.cfg +0 -0
  71. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/tests/test_cli_e2e.py +0 -0
  72. {qbraid_cli-0.16.10 → qbraid_cli-0.16.11}/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.10
3
+ Version: 0.16.11
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.27
33
+ Requires-Dist: qbraid-core>=0.6.29
34
34
  Provides-Extra: compute
35
- Requires-Dist: qbraid-core[compute]>=0.6.27; extra == "compute"
35
+ Requires-Dist: qbraid-core[compute]>=0.6.29; extra == "compute"
36
36
  Provides-Extra: envs
37
- Requires-Dist: qbraid-core[environments]>=0.6.27; extra == "envs"
37
+ Requires-Dist: qbraid-core[environments]>=0.6.29; 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.10"
7
+ version = "0.16.11"
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.27",
35
+ "qbraid-core>=0.6.29",
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.27"]
47
- envs = ["qbraid-core[environments]>=0.6.27"]
46
+ compute = ["qbraid-core[compute]>=0.6.29"]
47
+ envs = ["qbraid-core[environments]>=0.6.29"]
48
48
  dev = ["isort", "black", "pytest", "pytest-cov", "pytest-asyncio"]
49
49
 
50
50
  [project.scripts]
@@ -23,6 +23,8 @@ from rich.table import Table
23
23
 
24
24
  from qbraid_cli.handlers import handle_error, run_progress_task
25
25
 
26
+ from .work import register_work_commands
27
+
26
28
  agents_app = typer.Typer(
27
29
  help=(
28
30
  "Manage AI coding agent sessions.\n\n"
@@ -69,6 +71,8 @@ agents_app.add_typer(inbox_app, name="inbox")
69
71
 
70
72
  console = Console()
71
73
 
74
+ register_work_commands(agents_app)
75
+
72
76
 
73
77
  def _get_registry():
74
78
  """Get AgentSessionRegistryManager instance."""
@@ -0,0 +1,188 @@
1
+ # Copyright (c) 2026, qBraid Development Team
2
+ # All rights reserved.
3
+ """Read-only access to provider-reported background jobs and schedules."""
4
+
5
+ import json
6
+ import re
7
+ import shlex
8
+ import subprocess
9
+ import unicodedata
10
+ from datetime import datetime, timezone
11
+ from typing import Optional
12
+
13
+ import typer
14
+ from rich.console import Console
15
+ from rich.table import Table
16
+ from rich.text import Text
17
+
18
+ # C0 except tab, DEL, C1, and lone surrogates. A destination may run a Core
19
+ # that stored them verbatim, so rendering never trusts Core to have removed
20
+ # them: ESC/CSI/OSC/BEL can clear, hide, retitle, or hyperlink the terminal.
21
+ _UNSAFE = re.compile("[\x00-\x08\x0a-\x1f\x7f-\x9f\ud800-\udfff]")
22
+ _LINE_BREAKS = "\n\r\x0b\x0c\x85"
23
+ COMMAND_PREVIEW = 160
24
+
25
+
26
+ def _safe(value, multiline: bool = False) -> Text:
27
+ """Provider text as literal, terminal-inert text (never rich markup).
28
+
29
+ Line breaks become newlines (spaces in one-line cells); other controls
30
+ become U+FFFD in one pass, so nothing removed can join a new sequence.
31
+ """
32
+ text = value if isinstance(value, str) else ""
33
+ if multiline:
34
+ text = text.replace("\r\n", "\n")
35
+ text = _UNSAFE.sub(
36
+ lambda match: (("\n" if multiline else " ") if match.group() in _LINE_BREAKS else "\ufffd"),
37
+ text,
38
+ )
39
+ # Older destinations may retain bidi overrides, invisible formatting marks,
40
+ # and Unicode line/paragraph separators. Make them visible instead of
41
+ # joining their neighbours or allowing them to reorder IDs and statuses.
42
+ # Joiners remain in prose for ordinary scripts and emoji sequences.
43
+ return Text(
44
+ "".join(
45
+ (
46
+ "\ufffd"
47
+ if unicodedata.category(char) in {"Cf", "Zl", "Zp"}
48
+ and not (multiline and char in {"\u200c", "\u200d"})
49
+ else char
50
+ )
51
+ for char in text
52
+ )
53
+ )
54
+
55
+
56
+ def _detail(row: dict) -> Text:
57
+ """Description, plus a one-line preview of a job's command (full text in --json)."""
58
+ cell = _safe(row.get("prompt", row.get("description", "")), multiline=True)
59
+ command = row.get("command")
60
+ if isinstance(command, str) and command:
61
+ preview = _safe(command).plain
62
+ if len(preview) > COMMAND_PREVIEW:
63
+ preview = preview[: COMMAND_PREVIEW - 1] + "\u2026"
64
+ cell.append(("\n" if cell.plain else "") + "$ " + preview, style="dim")
65
+ return cell
66
+
67
+
68
+ class JobsReadError(ValueError):
69
+ """A safe, actionable error without provider prompts or transport output."""
70
+
71
+
72
+ def _work_model():
73
+ """Load the optional Core capability without breaking other CLI commands."""
74
+ try:
75
+ from qbraid_core.services.agents.work import SessionWork
76
+ except ImportError:
77
+ raise JobsReadError(
78
+ "Update qbraid-core to a release supporting session-work-v1 to list jobs."
79
+ ) from None
80
+ return SessionWork
81
+
82
+
83
+ def _read(session_id: str, compute: Optional[str]):
84
+ if compute:
85
+ if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,127}", compute):
86
+ raise JobsReadError("Use a configured SSH alias for --compute.")
87
+ command = "qbraid agents jobs --json -- " + shlex.quote(session_id)
88
+ result = subprocess.run(
89
+ ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "--", compute, command],
90
+ stdin=subprocess.DEVNULL,
91
+ capture_output=True,
92
+ text=True,
93
+ timeout=30,
94
+ check=False,
95
+ )
96
+ if result.returncode:
97
+ raise JobsReadError(
98
+ "Could not read jobs on that machine. Check SSH and the destination CLI version."
99
+ )
100
+ try:
101
+ data = json.loads(result.stdout)
102
+ except ValueError:
103
+ raise JobsReadError("The destination returned an invalid jobs response.") from None
104
+ if not isinstance(data, dict) or data.get("sessionId") != session_id:
105
+ raise JobsReadError("The destination did not return this session's jobs.")
106
+ return data
107
+ from qbraid_core.services.agents.machine_protocol import CAPABILITIES, MachineProtocol
108
+
109
+ if "session-work-v1" not in CAPABILITIES:
110
+ raise JobsReadError("Update qbraid-core to a release supporting session-work-v1 to list jobs.")
111
+ return MachineProtocol().dispatch({"op": "session_work", "sessionId": session_id})
112
+
113
+
114
+ def jobs(
115
+ session_id: str = typer.Argument(..., help="Exact qBraid session ID from agents list."),
116
+ compute: Optional[str] = typer.Option(None, "--compute", "-c", help="SSH alias for the owning machine."),
117
+ json_output: bool = typer.Option(False, "--json", help="Emit one JSON object; errors go to stderr."),
118
+ ):
119
+ """Inspect agent schedules and jobs.
120
+
121
+ This observes provider-owned work. It never creates a schedule, wakes an
122
+ agent, or treats missing telemetry as an empty list. Manage tasks through
123
+ the agent's own terminal. Claude reports snapshots after completing turns;
124
+ other providers report unsupported until they expose an inventory.
125
+ """
126
+ try:
127
+ model = _work_model()
128
+ data = _read(session_id, compute)
129
+ # Validate remote output too, before either JSON output or human tables.
130
+ # Model errors include input values, so never print their raw text.
131
+ data = model.model_validate(data).model_dump(by_alias=True)
132
+ if data["sessionId"] != session_id:
133
+ raise JobsReadError("The destination did not return this session's jobs.")
134
+ except Exception as error:
135
+ message = (
136
+ str(error)
137
+ if isinstance(error, JobsReadError)
138
+ else "Jobs could not be read for this exact session ID. Check it with "
139
+ "`qbraid agents list`; for a session on another machine, pass --compute <alias>."
140
+ )
141
+ typer.echo(message, err=True)
142
+ raise typer.Exit(1) from None
143
+ if json_output:
144
+ typer.echo(json.dumps(data, allow_nan=False))
145
+ return
146
+ console = Console()
147
+ snapshot = data.get("snapshot")
148
+ if data.get("state") != "observed" or not isinstance(snapshot, dict):
149
+ message = {
150
+ "unsupported": "This provider does not expose a supported jobs inventory.",
151
+ "not_observed": "No jobs snapshot has been reported for this runtime yet.",
152
+ }.get(
153
+ data.get("state"),
154
+ "No jobs snapshot can be read here. For a session on another machine, pass "
155
+ "--compute <alias>; otherwise its snapshot is unreadable, so check the agent's terminal.",
156
+ )
157
+ console.print(message, markup=False)
158
+ return
159
+ observed = datetime.fromtimestamp(snapshot["observedAt"], timezone.utc).isoformat()
160
+ console.print(
161
+ f"Last reported {observed}. This is an observation, not a live scheduler check.", markup=False
162
+ )
163
+ for key, title in (("schedules", "Schedules and loops"), ("jobs", "Background jobs")):
164
+ rows = snapshot.get(key)
165
+ if rows is None:
166
+ console.print(f"{title}: not reported.", markup=False)
167
+ continue
168
+ if not rows:
169
+ console.print(f"{title}: none reported.", markup=False)
170
+ continue
171
+ table = Table(title=title)
172
+ for heading in ("ID", "Schedule / status", "Prompt / description"):
173
+ table.add_column(heading)
174
+ for row in rows:
175
+ detail = _safe(row.get("schedule") if key == "schedules" else row.get("status"))
176
+ if not detail.plain:
177
+ detail = Text("unknown")
178
+ if key == "schedules":
179
+ detail.append(" (recurring)" if row.get("recurring") else " (once)")
180
+ table.add_row(_safe(row.get("id")), detail, _detail(row))
181
+ console.print(table)
182
+ if snapshot.get("truncated"):
183
+ console.print("Some entries were shortened or omitted. Check the agent's terminal for the full list.")
184
+
185
+
186
+ def register_work_commands(app: typer.Typer):
187
+ """Keep the agent CLI's command registration thin."""
188
+ app.command("jobs")(jobs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qbraid-cli
3
- Version: 0.16.10
3
+ Version: 0.16.11
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.27
33
+ Requires-Dist: qbraid-core>=0.6.29
34
34
  Provides-Extra: compute
35
- Requires-Dist: qbraid-core[compute]>=0.6.27; extra == "compute"
35
+ Requires-Dist: qbraid-core[compute]>=0.6.29; extra == "compute"
36
36
  Provides-Extra: envs
37
- Requires-Dist: qbraid-core[environments]>=0.6.27; extra == "envs"
37
+ Requires-Dist: qbraid-core[environments]>=0.6.29; extra == "envs"
38
38
  Provides-Extra: dev
39
39
  Requires-Dist: isort; extra == "dev"
40
40
  Requires-Dist: black; extra == "dev"
@@ -23,6 +23,7 @@ qbraid_cli/admin/validation.py
23
23
  qbraid_cli/agents/__init__.py
24
24
  qbraid_cli/agents/app.py
25
25
  qbraid_cli/agents/permission_response.py
26
+ qbraid_cli/agents/work.py
26
27
  qbraid_cli/ai/__init__.py
27
28
  qbraid_cli/ai/app.py
28
29
  qbraid_cli/ai/token.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.27
5
+ qbraid-core>=0.6.29
6
6
 
7
7
  [compute]
8
- qbraid-core[compute]>=0.6.27
8
+ qbraid-core[compute]>=0.6.29
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.27
18
+ qbraid-core[environments]>=0.6.29
File without changes
File without changes
File without changes