adaptorch-cli 0.1.0__tar.gz → 0.1.1__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.
- {adaptorch_cli-0.1.0/src/adaptorch_cli.egg-info → adaptorch_cli-0.1.1}/PKG-INFO +2 -2
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/pyproject.toml +2 -2
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli/cli.py +32 -3
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli/config_store.py +38 -2
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli/parser.py +23 -1
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1/src/adaptorch_cli.egg-info}/PKG-INFO +2 -2
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli.egg-info/SOURCES.txt +2 -1
- adaptorch_cli-0.1.1/src/adaptorch_cli.egg-info/requires.txt +1 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/tests/test_cli_commands.py +37 -0
- adaptorch_cli-0.1.1/tests/test_cli_oauth.py +76 -0
- adaptorch_cli-0.1.0/src/adaptorch_cli.egg-info/requires.txt +0 -1
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/LICENSE +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/README.md +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/setup.cfg +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli/__init__.py +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli/__main__.py +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli/py.typed +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli.egg-info/dependency_links.txt +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli.egg-info/entry_points.txt +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/src/adaptorch_cli.egg-info/top_level.txt +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/tests/test_cli_boundary.py +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/tests/test_cli_integration.py +0 -0
- {adaptorch_cli-0.1.0 → adaptorch_cli-0.1.1}/tests/test_cli_login_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adaptorch-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Command-line client for AdaptOrch
|
|
5
5
|
Author: ClassicMate
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -11,7 +11,7 @@ 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
|
|
14
|
+
Requires-Dist: adaptorch-client<0.2,>=0.1.2
|
|
15
15
|
Dynamic: license-file
|
|
16
16
|
|
|
17
17
|
# adaptorch-cli
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "adaptorch-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1"
|
|
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,<0.2"]
|
|
14
|
+
dependencies = ["adaptorch-client>=0.1.2,<0.2"]
|
|
15
15
|
|
|
16
16
|
[project.urls]
|
|
17
17
|
Homepage = "https://adaptorch.com"
|
|
@@ -13,6 +13,7 @@ from adaptorch_client import (
|
|
|
13
13
|
AdaptOrchAPIError,
|
|
14
14
|
AdaptOrchClient,
|
|
15
15
|
ClientConfig,
|
|
16
|
+
PollPolicy,
|
|
16
17
|
ProviderCredential,
|
|
17
18
|
validate_api_url,
|
|
18
19
|
)
|
|
@@ -39,7 +40,10 @@ def _reject_json_constant(_value: str) -> Never:
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def _finite_float(text: str) -> float:
|
|
42
|
-
|
|
43
|
+
try:
|
|
44
|
+
value = float(text)
|
|
45
|
+
except ValueError:
|
|
46
|
+
_reject_json_constant(text)
|
|
43
47
|
if not math.isfinite(value):
|
|
44
48
|
raise ValueError("non-finite JSON number")
|
|
45
49
|
return value
|
|
@@ -97,11 +101,14 @@ def _require_client(api_url: str) -> AdaptOrchClient:
|
|
|
97
101
|
|
|
98
102
|
|
|
99
103
|
def _submission_credential() -> ProviderCredential | None:
|
|
100
|
-
resolved,
|
|
104
|
+
resolved, source = config_store.resolve_provider_credential()
|
|
101
105
|
if resolved is None:
|
|
102
106
|
return None
|
|
103
107
|
provider, model, key = resolved
|
|
104
|
-
|
|
108
|
+
auth_type, account_id = config_store.resolve_provider_auth(source)
|
|
109
|
+
if auth_type is None and account_id is None:
|
|
110
|
+
return ProviderCredential(provider, model, key)
|
|
111
|
+
return ProviderCredential(provider, model, key, auth_type=auth_type, account_id=account_id)
|
|
105
112
|
|
|
106
113
|
|
|
107
114
|
def _read_secret(prompt: str, *, stdin_flag: bool) -> str:
|
|
@@ -241,6 +248,28 @@ def _run_command(
|
|
|
241
248
|
case "get":
|
|
242
249
|
run_id: str = args.run_id
|
|
243
250
|
return _result_payload(_require_client(api_url).get_run(run_id)), True
|
|
251
|
+
case "wait":
|
|
252
|
+
wait_run_id: str = args.run_id
|
|
253
|
+
timeout_seconds: float = args.timeout
|
|
254
|
+
interval_seconds: float = args.interval
|
|
255
|
+
if not math.isfinite(timeout_seconds) or timeout_seconds <= 0:
|
|
256
|
+
parser.error("--timeout must be finite positive seconds")
|
|
257
|
+
if not math.isfinite(interval_seconds) or interval_seconds <= 0:
|
|
258
|
+
parser.error("--interval must be finite positive seconds")
|
|
259
|
+
wait_result = _require_client(api_url).wait_for_run(
|
|
260
|
+
wait_run_id,
|
|
261
|
+
policy=PollPolicy(
|
|
262
|
+
timeout_seconds=timeout_seconds, interval_seconds=interval_seconds
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
wait_payload: dict[str, JSONValue] = {
|
|
266
|
+
"reason": wait_result.reason.value,
|
|
267
|
+
"polls": wait_result.polls,
|
|
268
|
+
"elapsed_seconds": wait_result.elapsed_seconds,
|
|
269
|
+
}
|
|
270
|
+
if wait_result.run is not None:
|
|
271
|
+
wait_payload["run"] = wait_result.run.to_payload()
|
|
272
|
+
return wait_payload, True
|
|
244
273
|
case "cancel":
|
|
245
274
|
cancel_run_id: str = args.run_id
|
|
246
275
|
reason: str | None = args.reason
|
|
@@ -27,6 +27,8 @@ _PROVIDER_KEY_MAP = {
|
|
|
27
27
|
"provider.name": "provider",
|
|
28
28
|
"provider.model": "model",
|
|
29
29
|
"provider.api_key": "api_key",
|
|
30
|
+
"provider.auth_type": "auth_type",
|
|
31
|
+
"provider.account_id": "account_id",
|
|
30
32
|
}
|
|
31
33
|
_CONFIGURABLE_KEYS = frozenset({"api_url", *_PROVIDER_KEY_MAP})
|
|
32
34
|
|
|
@@ -175,18 +177,50 @@ def resolve_provider_credential() -> tuple[tuple[str, str, str] | None, str]:
|
|
|
175
177
|
env_provider = os.environ.get("ADAPTORCH_PROVIDER", "").strip()
|
|
176
178
|
env_model = os.environ.get("ADAPTORCH_PROVIDER_MODEL", "").strip()
|
|
177
179
|
env_key = os.environ.get("ADAPTORCH_PROVIDER_API_KEY", "").strip()
|
|
178
|
-
if
|
|
180
|
+
if any(
|
|
181
|
+
(
|
|
182
|
+
env_provider,
|
|
183
|
+
env_model,
|
|
184
|
+
env_key,
|
|
185
|
+
os.environ.get("ADAPTORCH_PROVIDER_AUTH_TYPE"),
|
|
186
|
+
os.environ.get("ADAPTORCH_PROVIDER_ACCOUNT_ID"),
|
|
187
|
+
)
|
|
188
|
+
):
|
|
179
189
|
return (env_provider, env_model, env_key), "env"
|
|
180
190
|
provider_cfg = load_config().get("provider")
|
|
181
191
|
if isinstance(provider_cfg, dict):
|
|
182
192
|
provider = str(provider_cfg.get("provider") or "").strip()
|
|
183
193
|
model = str(provider_cfg.get("model") or "").strip()
|
|
184
194
|
key = str(provider_cfg.get("api_key") or "").strip()
|
|
185
|
-
|
|
195
|
+
has_auth_metadata = "auth_type" in provider_cfg or "account_id" in provider_cfg
|
|
196
|
+
if provider or model or key or has_auth_metadata:
|
|
186
197
|
return (provider, model, key), "config"
|
|
187
198
|
return None, "none"
|
|
188
199
|
|
|
189
200
|
|
|
201
|
+
def resolve_provider_auth(source: str) -> tuple[str | None, str | None]:
|
|
202
|
+
"""Read metadata from the same source as the token; never mix identities."""
|
|
203
|
+
if source == "env":
|
|
204
|
+
values = (
|
|
205
|
+
os.environ.get("ADAPTORCH_PROVIDER_AUTH_TYPE"),
|
|
206
|
+
os.environ.get("ADAPTORCH_PROVIDER_ACCOUNT_ID"),
|
|
207
|
+
)
|
|
208
|
+
else:
|
|
209
|
+
provider = load_config().get("provider")
|
|
210
|
+
if not isinstance(provider, dict):
|
|
211
|
+
return None, None
|
|
212
|
+
values = (provider.get("auth_type"), provider.get("account_id"))
|
|
213
|
+
|
|
214
|
+
def optional_text(value: object) -> str | None:
|
|
215
|
+
if value is None:
|
|
216
|
+
return None
|
|
217
|
+
if not isinstance(value, str):
|
|
218
|
+
raise ValueError("provider authentication metadata must be strings")
|
|
219
|
+
return value.strip() or None
|
|
220
|
+
|
|
221
|
+
return optional_text(values[0]), optional_text(values[1])
|
|
222
|
+
|
|
223
|
+
|
|
190
224
|
def mask_secret(value: str | None) -> str | None:
|
|
191
225
|
if not value:
|
|
192
226
|
return None
|
|
@@ -209,6 +243,8 @@ def config_view() -> dict[str, Any]:
|
|
|
209
243
|
"name": provider.get("provider"),
|
|
210
244
|
"model": provider.get("model"),
|
|
211
245
|
"api_key": mask_secret(provider_key if isinstance(provider_key, str) else None),
|
|
246
|
+
"auth_type": provider.get("auth_type"),
|
|
247
|
+
"account_id_configured": bool(provider.get("account_id")),
|
|
212
248
|
"source": resolve_provider_credential()[1],
|
|
213
249
|
},
|
|
214
250
|
"config_path": str(config_path()),
|
|
@@ -33,6 +33,28 @@ def _add_run_commands(parent: argparse._SubParsersAction[argparse.ArgumentParser
|
|
|
33
33
|
get = commands.add_parser("get", help="Get a run")
|
|
34
34
|
get.add_argument("run_id")
|
|
35
35
|
|
|
36
|
+
wait = commands.add_parser(
|
|
37
|
+
"wait",
|
|
38
|
+
help=(
|
|
39
|
+
"Read an existing run until it reaches a terminal status — the "
|
|
40
|
+
"CLI's form of the MCP resume_run_id contract. It never submits "
|
|
41
|
+
"another run; use it after an uncertain submit response."
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
wait.add_argument("run_id")
|
|
45
|
+
wait.add_argument(
|
|
46
|
+
"--timeout",
|
|
47
|
+
type=float,
|
|
48
|
+
default=120.0,
|
|
49
|
+
help="Seconds to keep polling before stopping (default: 120)",
|
|
50
|
+
)
|
|
51
|
+
wait.add_argument(
|
|
52
|
+
"--interval",
|
|
53
|
+
type=float,
|
|
54
|
+
default=1.0,
|
|
55
|
+
help="Seconds between status reads (default: 1)",
|
|
56
|
+
)
|
|
57
|
+
|
|
36
58
|
cancel = commands.add_parser("cancel", help="Cancel a run")
|
|
37
59
|
cancel.add_argument("run_id")
|
|
38
60
|
cancel.add_argument("--reason")
|
|
@@ -76,7 +98,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
76
98
|
config_commands.add_parser("get", help="Show the merged configuration (secrets masked)")
|
|
77
99
|
config_set = config_commands.add_parser(
|
|
78
100
|
"set",
|
|
79
|
-
help="Set api_url
|
|
101
|
+
help="Set api_url or provider.name/model/api_key/auth_type/account_id",
|
|
80
102
|
)
|
|
81
103
|
config_set.add_argument("key")
|
|
82
104
|
config_set.add_argument("value", nargs="?")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adaptorch-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Command-line client for AdaptOrch
|
|
5
5
|
Author: ClassicMate
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -11,7 +11,7 @@ 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
|
|
14
|
+
Requires-Dist: adaptorch-client<0.2,>=0.1.2
|
|
15
15
|
Dynamic: license-file
|
|
16
16
|
|
|
17
17
|
# adaptorch-cli
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
adaptorch-client<0.2,>=0.1.2
|
|
@@ -118,6 +118,43 @@ def test_run_get_prints_client_json(run_cli: CliRunner) -> None:
|
|
|
118
118
|
assert _json_stdout(result.stdout) == {"id": "run-1", "status": "RUNNING"}
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
def test_run_wait_reads_until_terminal_without_resubmitting(run_cli: CliRunner) -> None:
|
|
122
|
+
result = run_cli(
|
|
123
|
+
[
|
|
124
|
+
"--output",
|
|
125
|
+
"json",
|
|
126
|
+
"run",
|
|
127
|
+
"wait",
|
|
128
|
+
"run-1",
|
|
129
|
+
"--timeout",
|
|
130
|
+
"5",
|
|
131
|
+
"--interval",
|
|
132
|
+
"0.5",
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
assert result.returncode == 0
|
|
137
|
+
assert _json_stdout(result.stdout) == {
|
|
138
|
+
"reason": "terminal",
|
|
139
|
+
"polls": 1,
|
|
140
|
+
"elapsed_seconds": 0.01,
|
|
141
|
+
"run": {"id": "run-1", "status": "SUCCEEDED"},
|
|
142
|
+
}
|
|
143
|
+
calls = [json.loads(line) for line in result.client_log.splitlines()]
|
|
144
|
+
assert calls[-1] == {
|
|
145
|
+
"args": ["run-1"],
|
|
146
|
+
"kwargs": {"interval_seconds": 0.5, "timeout_seconds": 5.0},
|
|
147
|
+
"method": "wait_for_run",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_run_wait_rejects_non_positive_bounds(run_cli: CliRunner) -> None:
|
|
152
|
+
result = run_cli(["--output", "json", "run", "wait", "run-1", "--timeout", "0"])
|
|
153
|
+
|
|
154
|
+
assert result.returncode != 0
|
|
155
|
+
assert result.client_log.count("wait_for_run") == 0
|
|
156
|
+
|
|
157
|
+
|
|
121
158
|
def test_run_cancel_prints_client_json(run_cli: CliRunner) -> None:
|
|
122
159
|
result = run_cli(["--output", "json", "run", "cancel", "run-1"])
|
|
123
160
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from adaptorch_cli import cli, config_store
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture(autouse=True)
|
|
12
|
+
def isolated_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
13
|
+
monkeypatch.setenv("ADAPTORCH_CONFIG_DIR", str(tmp_path))
|
|
14
|
+
for name in (
|
|
15
|
+
"ADAPTORCH_PROVIDER",
|
|
16
|
+
"ADAPTORCH_PROVIDER_MODEL",
|
|
17
|
+
"ADAPTORCH_PROVIDER_API_KEY",
|
|
18
|
+
"ADAPTORCH_PROVIDER_AUTH_TYPE",
|
|
19
|
+
"ADAPTORCH_PROVIDER_ACCOUNT_ID",
|
|
20
|
+
):
|
|
21
|
+
monkeypatch.delenv(name, raising=False)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_cli_resolves_oauth_from_environment_without_persisting_token(
|
|
25
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
26
|
+
) -> None:
|
|
27
|
+
for name, value in {
|
|
28
|
+
"ADAPTORCH_PROVIDER": "openai_codex",
|
|
29
|
+
"ADAPTORCH_PROVIDER_MODEL": "gpt-5-codex",
|
|
30
|
+
"ADAPTORCH_PROVIDER_API_KEY": "synthetic-oauth-token",
|
|
31
|
+
"ADAPTORCH_PROVIDER_AUTH_TYPE": "oauth",
|
|
32
|
+
"ADAPTORCH_PROVIDER_ACCOUNT_ID": "caller-account",
|
|
33
|
+
}.items():
|
|
34
|
+
monkeypatch.setenv(name, value)
|
|
35
|
+
credential = cli._submission_credential()
|
|
36
|
+
assert credential is not None
|
|
37
|
+
assert credential.headers["X-Provider-Auth-Type"] == "oauth"
|
|
38
|
+
assert credential.headers["X-Provider-Account-Id"] == "caller-account"
|
|
39
|
+
assert not config_store.config_path().exists()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_cli_environment_credential_never_borrows_account_from_config(
|
|
43
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
44
|
+
) -> None:
|
|
45
|
+
config_store.save_config(
|
|
46
|
+
{
|
|
47
|
+
"provider": {
|
|
48
|
+
"provider": "openai_codex",
|
|
49
|
+
"model": "gpt-5-codex",
|
|
50
|
+
"api_key": "old-token",
|
|
51
|
+
"auth_type": "oauth",
|
|
52
|
+
"account_id": "old-account",
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
monkeypatch.setenv("ADAPTORCH_PROVIDER", "openai_codex")
|
|
57
|
+
monkeypatch.setenv("ADAPTORCH_PROVIDER_MODEL", "gpt-5-codex")
|
|
58
|
+
monkeypatch.setenv("ADAPTORCH_PROVIDER_API_KEY", "new-token")
|
|
59
|
+
credential = cli._submission_credential()
|
|
60
|
+
assert credential is not None
|
|
61
|
+
assert credential.account_id is None
|
|
62
|
+
assert credential.api_key == "new-token"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_cli_oauth_config_keys_and_view_are_safe() -> None:
|
|
66
|
+
config_store.set_config_value("provider.name", "openai_codex")
|
|
67
|
+
config_store.set_config_value("provider.model", "gpt-5-codex")
|
|
68
|
+
config_store.set_config_value("provider.api_key", "synthetic-oauth-token")
|
|
69
|
+
config_store.set_config_value("provider.auth_type", "oauth")
|
|
70
|
+
config_store.set_config_value("provider.account_id", "caller-account")
|
|
71
|
+
credential = cli._submission_credential()
|
|
72
|
+
assert credential is not None and credential.account_id == "caller-account"
|
|
73
|
+
view = json.dumps(config_store.config_view())
|
|
74
|
+
assert "synthetic-oauth-token" not in view
|
|
75
|
+
assert "caller-account" not in view
|
|
76
|
+
assert config_store.config_view()["provider"]["account_id_configured"] is True
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
adaptorch-client<0.2,>=0.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|