adaptorch-cli 0.1.1__tar.gz → 0.1.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 (24) hide show
  1. {adaptorch_cli-0.1.1/src/adaptorch_cli.egg-info → adaptorch_cli-0.1.3}/PKG-INFO +24 -2
  2. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/README.md +22 -0
  3. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/pyproject.toml +2 -2
  4. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli/cli.py +15 -0
  5. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3/src/adaptorch_cli.egg-info}/PKG-INFO +24 -2
  6. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli.egg-info/SOURCES.txt +2 -1
  7. adaptorch_cli-0.1.3/src/adaptorch_cli.egg-info/requires.txt +1 -0
  8. adaptorch_cli-0.1.3/tests/test_cli_wait_exit_codes.py +182 -0
  9. adaptorch_cli-0.1.1/src/adaptorch_cli.egg-info/requires.txt +0 -1
  10. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/LICENSE +0 -0
  11. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/setup.cfg +0 -0
  12. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli/__init__.py +0 -0
  13. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli/__main__.py +0 -0
  14. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli/config_store.py +0 -0
  15. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli/parser.py +0 -0
  16. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli/py.typed +0 -0
  17. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli.egg-info/dependency_links.txt +0 -0
  18. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli.egg-info/entry_points.txt +0 -0
  19. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/src/adaptorch_cli.egg-info/top_level.txt +0 -0
  20. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/tests/test_cli_boundary.py +0 -0
  21. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/tests/test_cli_commands.py +0 -0
  22. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/tests/test_cli_integration.py +0 -0
  23. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/tests/test_cli_login_config.py +0 -0
  24. {adaptorch_cli-0.1.1 → adaptorch_cli-0.1.3}/tests/test_cli_oauth.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adaptorch-cli
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Command-line client for AdaptOrch
5
5
  Author: ClassicMate
6
6
  License-Expression: LicenseRef-Proprietary
@@ -11,11 +11,17 @@ Project-URL: Documentation, https://adaptorch.com/mcp-docs
11
11
  Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: adaptorch-client<0.2,>=0.1.2
14
+ Requires-Dist: adaptorch-client<0.2,>=0.1.3
15
15
  Dynamic: license-file
16
16
 
17
17
  # adaptorch-cli
18
18
 
19
+ Run/evidence JSON may include the SDK's safe, versioned `verification_diagnostics`.
20
+ Project reports remain untrusted observations. Invalid optional metadata is
21
+ omitted by the SDK, including in CLI JSON; it does not turn a valid run response
22
+ into a CLI error. Existing failed/cancelled/inconclusive exits stay 8/9/10,
23
+ and a stopped wait still exits 10. PDF tools are not global CLI prerequisites.
24
+
19
25
  Independent AdaptOrch SaaS CLI. Installs the `adaptorchctl` command and uses `adaptorch-client`; it does not invoke `adaptorch` or `adaptorch-mcp`.
20
26
 
21
27
  Log in once and every command picks the credential up:
@@ -41,6 +47,22 @@ A git push does not replace a previously published PyPI version.
41
47
 
42
48
  See [`../../docs/adaptorchctl-usage.ko.md`](../../docs/adaptorchctl-usage.ko.md) for usage.
43
49
 
50
+ ## Bounded wait exit codes
51
+
52
+ `adaptorchctl run wait RUN_ID` emits the last observation as JSON. A terminal
53
+ `SUCCEEDED` exits 0, `FAILED` exits 8, and `CANCELLED` exits 9. These codes describe
54
+ run lifecycle, not result correctness or evidence verification.
55
+
56
+ A `deadline`, `poll_limit`, `unsupported_status`, or unrecognized stop reason exits
57
+ 10, even if the last observation looks terminal. Missing or unsupported terminal
58
+ observations also exit 10. This means the observation is inconclusive, not that
59
+ the server-side run failed. Inspect `reason` and retain the JSON; continue with the
60
+ same run ID when appropriate. Waiting never resubmits or cancels a run.
61
+
62
+ Automation compatibility: `run wait` previously returned 0 for these observations.
63
+ Scripts using `set -e` must now handle nonzero exits. `run get` keeps its existing
64
+ exit-code behavior; submit and cancel still report command success separately.
65
+
44
66
  ## License
45
67
 
46
68
  Proprietary — Copyright ClassicMate. All rights reserved. See [LICENSE](https://github.com/dmae97/Adaptorch-MCP/blob/main/LICENSE).
@@ -1,5 +1,11 @@
1
1
  # adaptorch-cli
2
2
 
3
+ Run/evidence JSON may include the SDK's safe, versioned `verification_diagnostics`.
4
+ Project reports remain untrusted observations. Invalid optional metadata is
5
+ omitted by the SDK, including in CLI JSON; it does not turn a valid run response
6
+ into a CLI error. Existing failed/cancelled/inconclusive exits stay 8/9/10,
7
+ and a stopped wait still exits 10. PDF tools are not global CLI prerequisites.
8
+
3
9
  Independent AdaptOrch SaaS CLI. Installs the `adaptorchctl` command and uses `adaptorch-client`; it does not invoke `adaptorch` or `adaptorch-mcp`.
4
10
 
5
11
  Log in once and every command picks the credential up:
@@ -25,6 +31,22 @@ A git push does not replace a previously published PyPI version.
25
31
 
26
32
  See [`../../docs/adaptorchctl-usage.ko.md`](../../docs/adaptorchctl-usage.ko.md) for usage.
27
33
 
34
+ ## Bounded wait exit codes
35
+
36
+ `adaptorchctl run wait RUN_ID` emits the last observation as JSON. A terminal
37
+ `SUCCEEDED` exits 0, `FAILED` exits 8, and `CANCELLED` exits 9. These codes describe
38
+ run lifecycle, not result correctness or evidence verification.
39
+
40
+ A `deadline`, `poll_limit`, `unsupported_status`, or unrecognized stop reason exits
41
+ 10, even if the last observation looks terminal. Missing or unsupported terminal
42
+ observations also exit 10. This means the observation is inconclusive, not that
43
+ the server-side run failed. Inspect `reason` and retain the JSON; continue with the
44
+ same run ID when appropriate. Waiting never resubmits or cancels a run.
45
+
46
+ Automation compatibility: `run wait` previously returned 0 for these observations.
47
+ Scripts using `set -e` must now handle nonzero exits. `run get` keeps its existing
48
+ exit-code behavior; submit and cancel still report command success separately.
49
+
28
50
  ## License
29
51
 
30
52
  Proprietary — Copyright ClassicMate. All rights reserved. See [LICENSE](https://github.com/dmae97/Adaptorch-MCP/blob/main/LICENSE).
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "adaptorch-cli"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "Command-line client for AdaptOrch"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
  license = "LicenseRef-Proprietary"
12
12
  license-files = ["LICENSE"]
13
13
  authors = [{ name = "ClassicMate" }]
14
- dependencies = ["adaptorch-client>=0.1.2,<0.2"]
14
+ dependencies = ["adaptorch-client>=0.1.3,<0.2"]
15
15
 
16
16
  [project.urls]
17
17
  Homepage = "https://adaptorch.com"
@@ -347,6 +347,19 @@ def _run_status_exit(payload: JSONMapping) -> int:
347
347
  return 0
348
348
 
349
349
 
350
+ def _wait_status_exit(payload: JSONMapping) -> int:
351
+ """Map a bounded observation without treating a stopped wait as success."""
352
+ if payload.get("reason") != "terminal":
353
+ return 10
354
+ run = payload.get("run")
355
+ if not isinstance(run, dict):
356
+ return 10
357
+ status = run.get("status")
358
+ if not isinstance(status, str):
359
+ return 10
360
+ return {"succeeded": 0, "failed": 8, "cancelled": 9}.get(status.lower(), 10)
361
+
362
+
350
363
  def main(argv: Sequence[str] | None = None) -> int:
351
364
  parser = build_parser()
352
365
  args = parse_args(argv)
@@ -362,6 +375,8 @@ def main(argv: Sequence[str] | None = None) -> int:
362
375
  except ValueError:
363
376
  print("response contained non-encodable JSON values", file=sys.stderr)
364
377
  return 7
378
+ if args.command == "run" and args.run_command == "wait":
379
+ return _wait_status_exit(payload)
365
380
  return _run_status_exit(payload) if check_run_status else 0
366
381
  # pi-lens-ignore: unreachable-except
367
382
  except KeyboardInterrupt:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adaptorch-cli
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Command-line client for AdaptOrch
5
5
  Author: ClassicMate
6
6
  License-Expression: LicenseRef-Proprietary
@@ -11,11 +11,17 @@ Project-URL: Documentation, https://adaptorch.com/mcp-docs
11
11
  Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: adaptorch-client<0.2,>=0.1.2
14
+ Requires-Dist: adaptorch-client<0.2,>=0.1.3
15
15
  Dynamic: license-file
16
16
 
17
17
  # adaptorch-cli
18
18
 
19
+ Run/evidence JSON may include the SDK's safe, versioned `verification_diagnostics`.
20
+ Project reports remain untrusted observations. Invalid optional metadata is
21
+ omitted by the SDK, including in CLI JSON; it does not turn a valid run response
22
+ into a CLI error. Existing failed/cancelled/inconclusive exits stay 8/9/10,
23
+ and a stopped wait still exits 10. PDF tools are not global CLI prerequisites.
24
+
19
25
  Independent AdaptOrch SaaS CLI. Installs the `adaptorchctl` command and uses `adaptorch-client`; it does not invoke `adaptorch` or `adaptorch-mcp`.
20
26
 
21
27
  Log in once and every command picks the credential up:
@@ -41,6 +47,22 @@ A git push does not replace a previously published PyPI version.
41
47
 
42
48
  See [`../../docs/adaptorchctl-usage.ko.md`](../../docs/adaptorchctl-usage.ko.md) for usage.
43
49
 
50
+ ## Bounded wait exit codes
51
+
52
+ `adaptorchctl run wait RUN_ID` emits the last observation as JSON. A terminal
53
+ `SUCCEEDED` exits 0, `FAILED` exits 8, and `CANCELLED` exits 9. These codes describe
54
+ run lifecycle, not result correctness or evidence verification.
55
+
56
+ A `deadline`, `poll_limit`, `unsupported_status`, or unrecognized stop reason exits
57
+ 10, even if the last observation looks terminal. Missing or unsupported terminal
58
+ observations also exit 10. This means the observation is inconclusive, not that
59
+ the server-side run failed. Inspect `reason` and retain the JSON; continue with the
60
+ same run ID when appropriate. Waiting never resubmits or cancels a run.
61
+
62
+ Automation compatibility: `run wait` previously returned 0 for these observations.
63
+ Scripts using `set -e` must now handle nonzero exits. `run get` keeps its existing
64
+ exit-code behavior; submit and cancel still report command success separately.
65
+
44
66
  ## License
45
67
 
46
68
  Proprietary — Copyright ClassicMate. All rights reserved. See [LICENSE](https://github.com/dmae97/Adaptorch-MCP/blob/main/LICENSE).
@@ -17,4 +17,5 @@ tests/test_cli_boundary.py
17
17
  tests/test_cli_commands.py
18
18
  tests/test_cli_integration.py
19
19
  tests/test_cli_login_config.py
20
- tests/test_cli_oauth.py
20
+ tests/test_cli_oauth.py
21
+ tests/test_cli_wait_exit_codes.py
@@ -0,0 +1 @@
1
+ adaptorch-client<0.2,>=0.1.3
@@ -0,0 +1,182 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections.abc import Callable
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+ from adaptorch_client import AdaptOrchClient, ClientConfig, PollPolicy, Run, RunPollResult, polling
9
+ from adaptorch_client import client as client_module
10
+ from adaptorch_client.transport import HTTPTransport, RequestSpec
11
+ from cli_test_support import CliRunner
12
+
13
+ from adaptorch_cli import cli
14
+
15
+
16
+ @pytest.mark.parametrize(
17
+ ("reason", "status", "expected"),
18
+ [
19
+ ("terminal", "SUCCEEDED", 0),
20
+ ("terminal", "sUcCeEdEd", 0),
21
+ ("terminal", "FAILED", 8),
22
+ ("terminal", "failed", 8),
23
+ ("terminal", "CANCELLED", 9),
24
+ ("terminal", "cancelled", 9),
25
+ ("deadline", "RUNNING", 10),
26
+ ("deadline", "SUCCEEDED", 10),
27
+ ("deadline", "FAILED", 10),
28
+ ("poll_limit", "RUNNING", 10),
29
+ ("unsupported_status", "INCONCLUSIVE", 10),
30
+ ("unsupported_status", "FUTURE_STATUS", 10),
31
+ ("future_reason", "SUCCEEDED", 10),
32
+ ("terminal", "RUNNING", 10),
33
+ ("terminal", "FUTURE_STATUS", 10),
34
+ ],
35
+ )
36
+ def test_wait_exit_preserves_observation_and_never_resubmits(
37
+ run_cli: CliRunner,
38
+ monkeypatch: pytest.MonkeyPatch,
39
+ reason: str,
40
+ status: str,
41
+ expected: int,
42
+ ) -> None:
43
+ monkeypatch.setenv("FAKE_WAIT_REASON", reason)
44
+ monkeypatch.setenv("FAKE_RUN_STATUS", status)
45
+ result = run_cli(["run", "wait", "run-1"])
46
+
47
+ assert result.returncode == expected
48
+ assert json.loads(result.stdout) == {
49
+ "reason": reason,
50
+ "polls": 1,
51
+ "elapsed_seconds": 0.01,
52
+ "run": {"id": "run-1", "status": status},
53
+ }
54
+ assert result.stdout.count("\n") == 1
55
+ assert result.stderr == ""
56
+ methods = [json.loads(line)["method"] for line in result.client_log.splitlines()]
57
+ assert methods == ["init", "wait_for_run"]
58
+
59
+
60
+ @pytest.mark.parametrize(
61
+ "payload",
62
+ [
63
+ {},
64
+ {"reason": None, "run": {"status": "SUCCEEDED"}},
65
+ {"reason": 1, "run": {"status": "SUCCEEDED"}},
66
+ {"reason": "future_reason", "run": {"status": "SUCCEEDED"}},
67
+ {"reason": "terminal"},
68
+ {"reason": "terminal", "run": None},
69
+ {"reason": "terminal", "run": []},
70
+ {"reason": "terminal", "run": {}},
71
+ {"reason": "terminal", "run": {"status": None}},
72
+ {"reason": "terminal", "run": {"status": 1}},
73
+ {"reason": "terminal", "run": {"status": ""}},
74
+ {"reason": "terminal", "run": {"data": {"status": "SUCCEEDED"}}},
75
+ {"reason": "deadline"},
76
+ {"reason": "poll_limit"},
77
+ {"reason": "unsupported_status"},
78
+ ],
79
+ )
80
+ def test_wait_unknown_or_missing_observation_is_inconclusive(
81
+ payload: cli.JSONMapping,
82
+ ) -> None:
83
+ assert cli._wait_status_exit(payload) == 10
84
+
85
+
86
+ def test_wait_lifecycle_does_not_claim_result_correctness() -> None:
87
+ assert cli._wait_status_exit(
88
+ {"reason": "terminal", "run": {"status": "SUCCEEDED", "result_status": "FAILED"}}
89
+ ) == 0
90
+
91
+
92
+ def test_wait_does_not_reinterpret_server_cancellation_encoding() -> None:
93
+ assert cli._wait_status_exit(
94
+ {"reason": "terminal", "run": {"status": "FAILED", "error_class": "CANCELLED"}}
95
+ ) == 8
96
+
97
+
98
+ @pytest.mark.parametrize(
99
+ ("status", "reason", "expected", "timeout", "latency", "count"),
100
+ [
101
+ ("SUCCEEDED", "terminal", 0, "5", 0.0, 1),
102
+ ("FAILED", "terminal", 8, "5", 0.0, 1),
103
+ ("CANCELLED", "terminal", 9, "5", 0.0, 1),
104
+ ("RUNNING", "deadline", 10, "0.5", 0.0, 1),
105
+ ("RUNNING", "poll_limit", 10, "200", 0.0, 100),
106
+ ("NEW_STATUS", "unsupported_status", 10, "5", 0.0, 1),
107
+ ("INCONCLUSIVE", "unsupported_status", 10, "5", 0.0, 1),
108
+ ("SUCCEEDED", "deadline", 10, "0.5", 0.5, 1),
109
+ ],
110
+ )
111
+ def test_wait_uses_real_sdk_polling_without_writing_or_resubmitting(
112
+ monkeypatch: pytest.MonkeyPatch,
113
+ tmp_path: Path,
114
+ capsys: pytest.CaptureFixture[str],
115
+ status: str,
116
+ reason: str,
117
+ expected: int,
118
+ timeout: str,
119
+ latency: float,
120
+ count: int,
121
+ ) -> None:
122
+ elapsed = 0.0
123
+ requests: list[RequestSpec] = []
124
+
125
+ def clock() -> float:
126
+ return elapsed
127
+
128
+ def sleep(duration: float) -> None:
129
+ nonlocal elapsed
130
+ elapsed += duration
131
+
132
+ def request(_transport: HTTPTransport, spec: RequestSpec) -> cli.JSONMapping:
133
+ nonlocal elapsed
134
+ requests.append(spec)
135
+ elapsed += latency
136
+ return {"run_id": "run-1", "status": status}
137
+
138
+ def poll(fetch: Callable[[float], Run], policy: PollPolicy) -> RunPollResult:
139
+ return polling.wait_for_run(fetch, policy, clock=clock, sleep=sleep)
140
+
141
+ client = AdaptOrchClient(ClientConfig("https://api.example.test", "ado_test"))
142
+ monkeypatch.setenv("ADAPTORCH_CONFIG_DIR", str(tmp_path / "isolated-config"))
143
+ monkeypatch.setattr(cli, "_require_client", lambda _api_url: client)
144
+ monkeypatch.setattr(HTTPTransport, "request", request)
145
+ monkeypatch.setattr(client_module, "poll_run", poll)
146
+
147
+ code = cli.main(["run", "wait", "run-1", "--timeout", timeout, "--interval", "1"])
148
+
149
+ captured = capsys.readouterr()
150
+ assert code == expected
151
+ assert json.loads(captured.out) == {
152
+ "reason": reason,
153
+ "polls": count,
154
+ "elapsed_seconds": elapsed,
155
+ "run": {"run_id": "run-1", "status": status},
156
+ }
157
+ assert captured.out.count("\n") == 1
158
+ assert captured.err == ""
159
+ assert len(requests) == count
160
+ assert all((spec.method, spec.path) == ("GET", "/v1/runs/run-1") for spec in requests)
161
+ assert all(spec.payload is None and spec.provider_credential is None for spec in requests)
162
+
163
+
164
+ @pytest.mark.parametrize(
165
+ ("status", "expected"),
166
+ [("SUCCEEDED", 0), ("RUNNING", 0), ("NEW_STATUS", 0), ("FAILED", 8), ("CANCELLED", 9)],
167
+ )
168
+ def test_get_retains_its_mapping_with_additive_wait_like_fields(
169
+ monkeypatch: pytest.MonkeyPatch,
170
+ tmp_path: Path,
171
+ capsys: pytest.CaptureFixture[str],
172
+ status: str,
173
+ expected: int,
174
+ ) -> None:
175
+ payload: cli.JSONMapping = {"run_id": "run-1", "status": status, "reason": "deadline"}
176
+ monkeypatch.setenv("ADAPTORCH_CONFIG_DIR", str(tmp_path / "isolated-config"))
177
+ monkeypatch.setattr(cli, "_execute", lambda *_args: (payload, True))
178
+
179
+ assert cli.main(["run", "get", "run-1"]) == expected
180
+ captured = capsys.readouterr()
181
+ assert json.loads(captured.out) == payload
182
+ assert captured.err == ""
@@ -1 +0,0 @@
1
- adaptorch-client<0.2,>=0.1.2
File without changes
File without changes