coding-cli-runtime 0.1.0__tar.gz → 0.2.0__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 (40) hide show
  1. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/CHANGELOG.md +30 -18
  2. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/PKG-INFO +34 -1
  3. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/README.md +33 -0
  4. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/pyproject.toml +2 -2
  5. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/__init__.py +27 -1
  6. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/auth.py +25 -9
  7. coding_cli_runtime-0.2.0/src/coding_cli_runtime/provider_contracts.py +476 -0
  8. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime.egg-info/PKG-INFO +34 -1
  9. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime.egg-info/SOURCES.txt +3 -0
  10. coding_cli_runtime-0.2.0/tests/test_coverage_gaps.py +905 -0
  11. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/tests/test_packaging.py +9 -3
  12. coding_cli_runtime-0.2.0/tests/test_provider_contracts.py +322 -0
  13. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/CONTRIBUTING.md +0 -0
  14. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/LICENSE +0 -0
  15. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/MANIFEST.in +0 -0
  16. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/setup.cfg +0 -0
  17. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/codex_cli.py +0 -0
  18. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/contracts.py +0 -0
  19. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/copilot_reasoning_baseline.json +0 -0
  20. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/copilot_reasoning_logs.py +0 -0
  21. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/failure_classification.py +0 -0
  22. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/json_io.py +0 -0
  23. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/provider_controls.py +0 -0
  24. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/provider_specs.py +0 -0
  25. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/py.typed +0 -0
  26. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/reasoning.py +0 -0
  27. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/redaction.py +0 -0
  28. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/schema_validation.py +0 -0
  29. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/schemas/normalized_run_result.v1.json +0 -0
  30. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/schemas/reasoning_metadata.v1.json +0 -0
  31. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/session_execution.py +0 -0
  32. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/session_logs.py +0 -0
  33. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime/subprocess_runner.py +0 -0
  34. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime.egg-info/dependency_links.txt +0 -0
  35. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/src/coding_cli_runtime.egg-info/top_level.txt +0 -0
  36. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/tests/test_copilot_reasoning_logs.py +0 -0
  37. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/tests/test_package_resources.py +0 -0
  38. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/tests/test_playground_probe_smoke.py +0 -0
  39. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/tests/test_provider_catalog_resolution.py +0 -0
  40. {coding_cli_runtime-0.1.0 → coding_cli_runtime-0.2.0}/tests/test_runtime_parity.py +0 -0
@@ -6,38 +6,50 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0] - 2026-04-08
10
+
11
+ ### Added
12
+ - **ProviderContract API** — structured, nested metadata for all four provider CLIs
13
+ (Claude, Codex, Gemini, Copilot). Composed of `AuthContract`, `PathContract`,
14
+ `HeadlessContract`, `PromptTransport`, `ApprovalContract`, `SandboxContract`.
15
+ - `get_provider_contract(provider_id)` — returns structured contract for a provider.
16
+ - `build_env_overlay(contract, api_key, base_url)` — builds provider-specific env
17
+ var overlay from contract metadata.
18
+ - `resolve_config_paths(contract, containerized)` — resolves host and container
19
+ config directory paths.
20
+ - `render_prompt(transport, prompt)` — resolves prompt delivery into argv args +
21
+ stdin text based on provider transport mode.
22
+ - `PromptPayload` dataclass for resolved prompt delivery.
23
+ - `__version__` attribute in `coding_cli_runtime`.
24
+ - `CONTRIBUTING.md`, `MANIFEST.in`, `.pre-commit-config.yaml`.
25
+ - PyPI / Python / Build / License badges in `README.md`.
26
+ - `bump-my-version` configuration syncing `pyproject.toml` and `__init__.py`.
27
+ - `ruff`, `mypy` (strict), and `pytest-cov` added to dev dependencies.
28
+ - CI quality gates: ruff check, ruff format, mypy, pytest-cov.
29
+ - README section documenting the new ProviderContract API with examples.
30
+ - 75 new tests for provider contracts, helpers, internal builder, failure
31
+ classification, codex_cli, schema validation (including nested), reasoning,
32
+ redaction, json_io, provider_controls, and auth. Package coverage 47% → 62%.
33
+
9
34
  ### Changed
10
35
  - Consolidated `shared_cli_runtime` into `coding_cli_runtime`. The package now
11
36
  ships a single top-level package; the `shared_cli_runtime` directory is removed.
12
37
  - `MANIFEST.in` and docs updated to reference `coding_cli_runtime` paths.
13
- - Minimum Python version remains `>=3.10`.
14
38
  - `run_interactive_session()` observability kwargs (`provider_label`, `job_name`,
15
39
  `phase_tag`, `process_label`, `timeout_seconds`) now have sensible defaults so
16
40
  external callers don't need to supply internal batch-system labels.
17
41
  - Provider model catalogs are now resolved with a three-tier fallback:
18
42
  user override file > live CLI discovery > hardcoded fallback.
19
- - **Codex**: reads `~/.codex/models_cache.json` (auto-refreshed by the CLI)
20
- when present; falls back to the hardcoded catalog on any parse failure.
21
- - **All providers**: place a JSON file at
22
- `~/.config/coding-cli-runtime/providers/<provider>.json` (or set
23
- `CODING_CLI_RUNTIME_CONFIG_DIR`) to override the model list and default
24
- model without waiting for a package update.
25
-
26
- ### Added
27
- - `__version__` attribute in both `coding_cli_runtime` and `shared_cli_runtime`.
28
- - `CONTRIBUTING.md`, `MANIFEST.in`, `.pre-commit-config.yaml`.
29
- - PyPI / Python / Build / License badges in `README.md`.
30
- - `bump-my-version` configuration syncing `pyproject.toml` and both `__init__.py` files.
31
- - `ruff`, `mypy` (strict), and `pytest-cov` added to dev dependencies.
32
- - CI quality gates: ruff check, ruff format, mypy, pytest-cov.
33
-
34
- ### Changed
43
+ - `auth.py`: `_PROVIDER_ENV_HINTS` now derived from `provider_contracts.py`
44
+ (single source of truth for auth env var names).
35
45
  - `CliRunResult.command` type widened from `tuple[str, ...]` to `Sequence[str]`.
36
46
  - Publish workflow path corrected (`shared-cli-runtime` → `coding-cli-runtime`).
37
47
 
38
48
  ### Fixed
39
- - mypy strict compliance: return-type annotations, per-module overrides, targeted type-ignore comments.
49
+ - mypy strict compliance: return-type annotations, per-module overrides.
40
50
  - ruff lint and format compliance across all source and test files.
51
+ - Copilot BYOK (`COPILOT_PROVIDER_API_KEY`) now discoverable via contract
52
+ but not reported as "required" in `resolve_auth()` — BYOK is opt-in.
41
53
 
42
54
  ## [0.1.0] - 2026-04-07
43
55
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-cli-runtime
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Reusable CLI runtime primitives for provider-backed automation workflows
5
5
  Author-email: LLM Eval maintainers <llm-eval-maintainers@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -148,6 +148,38 @@ else:
148
148
  Works for all four providers. Recognizes auth failures, rate limits,
149
149
  network transients, and other provider-specific error patterns.
150
150
 
151
+ ### Look up provider contract metadata
152
+
153
+ ```python
154
+ from coding_cli_runtime import get_provider_contract, build_env_overlay, resolve_config_paths, render_prompt
155
+
156
+ # Get structured metadata for any supported provider
157
+ contract = get_provider_contract("claude")
158
+ print(contract.binary) # "claude"
159
+ print(contract.auth.api_key_env_var) # "CLAUDE_API_KEY"
160
+ print(contract.paths.config_dir) # "~/.claude"
161
+ print(contract.headless.approval.flag) # "--dangerously-skip-permissions"
162
+
163
+ # Build env var overlay for subprocess
164
+ env = build_env_overlay(contract, api_key="sk-...", base_url="https://custom.example.com")
165
+ # {"CLAUDE_API_KEY": "sk-...", "ANTHROPIC_BASE_URL": "https://custom.example.com"}
166
+
167
+ # Resolve config paths for container mounts
168
+ host_dir, container_dir = resolve_config_paths(contract, containerized=True)
169
+ # ("/home/user/.claude", "/root/.claude")
170
+
171
+ # Resolve prompt delivery (stdin vs flag vs activation)
172
+ payload = render_prompt(contract.headless.prompt, "Fix the bug")
173
+ # payload.args = () (stdin delivery for Claude)
174
+ # payload.stdin_text = "Fix the bug"
175
+ ```
176
+
177
+ `ProviderContract` is structured as nested sub-contracts
178
+ (`AuthContract`, `PathContract`, `HeadlessContract`) so consumers
179
+ can drill into whichever aspect they need. This is reference metadata,
180
+ not a command-construction control plane — consumers keep their own
181
+ command assembly and adopt contract fields selectively.
182
+
151
183
  ## Key types
152
184
 
153
185
  | Type | Purpose |
@@ -156,6 +188,7 @@ network transients, and other provider-specific error patterns.
156
188
  | `CliRunResult` | Result: returncode, stdout/stderr, duration, error code |
157
189
  | `ErrorCode` | `none` · `spawn_failed` · `timed_out` · `non_zero_exit` |
158
190
  | `ProviderSpec` | Provider catalog entry with models, controls, defaults |
191
+ | `ProviderContract` | Structured provider CLI metadata (auth, paths, headless launch) |
159
192
  | `FailureClassification` | Classified error with retryable flag and category |
160
193
 
161
194
  `run_interactive_session()` manages long-running CLI processes with
@@ -122,6 +122,38 @@ else:
122
122
  Works for all four providers. Recognizes auth failures, rate limits,
123
123
  network transients, and other provider-specific error patterns.
124
124
 
125
+ ### Look up provider contract metadata
126
+
127
+ ```python
128
+ from coding_cli_runtime import get_provider_contract, build_env_overlay, resolve_config_paths, render_prompt
129
+
130
+ # Get structured metadata for any supported provider
131
+ contract = get_provider_contract("claude")
132
+ print(contract.binary) # "claude"
133
+ print(contract.auth.api_key_env_var) # "CLAUDE_API_KEY"
134
+ print(contract.paths.config_dir) # "~/.claude"
135
+ print(contract.headless.approval.flag) # "--dangerously-skip-permissions"
136
+
137
+ # Build env var overlay for subprocess
138
+ env = build_env_overlay(contract, api_key="sk-...", base_url="https://custom.example.com")
139
+ # {"CLAUDE_API_KEY": "sk-...", "ANTHROPIC_BASE_URL": "https://custom.example.com"}
140
+
141
+ # Resolve config paths for container mounts
142
+ host_dir, container_dir = resolve_config_paths(contract, containerized=True)
143
+ # ("/home/user/.claude", "/root/.claude")
144
+
145
+ # Resolve prompt delivery (stdin vs flag vs activation)
146
+ payload = render_prompt(contract.headless.prompt, "Fix the bug")
147
+ # payload.args = () (stdin delivery for Claude)
148
+ # payload.stdin_text = "Fix the bug"
149
+ ```
150
+
151
+ `ProviderContract` is structured as nested sub-contracts
152
+ (`AuthContract`, `PathContract`, `HeadlessContract`) so consumers
153
+ can drill into whichever aspect they need. This is reference metadata,
154
+ not a command-construction control plane — consumers keep their own
155
+ command assembly and adopt contract fields selectively.
156
+
125
157
  ## Key types
126
158
 
127
159
  | Type | Purpose |
@@ -130,6 +162,7 @@ network transients, and other provider-specific error patterns.
130
162
  | `CliRunResult` | Result: returncode, stdout/stderr, duration, error code |
131
163
  | `ErrorCode` | `none` · `spawn_failed` · `timed_out` · `non_zero_exit` |
132
164
  | `ProviderSpec` | Provider catalog entry with models, controls, defaults |
165
+ | `ProviderContract` | Structured provider CLI metadata (auth, paths, headless launch) |
133
166
  | `FailureClassification` | Classified error with retryable flag and category |
134
167
 
135
168
  `run_interactive_session()` manages long-running CLI processes with
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "coding-cli-runtime"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Reusable CLI runtime primitives for provider-backed automation workflows"
9
9
  readme = {file = "README.md", content-type = "text/markdown"}
10
10
  license = "MIT"
@@ -94,7 +94,7 @@ disallow_untyped_defs = false
94
94
  warn_return_any = false
95
95
 
96
96
  [tool.bumpversion]
97
- current_version = "0.1.0"
97
+ current_version = "0.2.0"
98
98
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
99
99
  serialize = ["{major}.{minor}.{patch}"]
100
100
  commit = true
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.1.0"
5
+ __version__ = "0.2.0"
6
6
 
7
7
  from .auth import AuthResolution, resolve_auth
8
8
  from .codex_cli import CodexExecSpec, build_codex_exec_spec
@@ -15,6 +15,20 @@ from .contracts import (
15
15
  ErrorCode,
16
16
  )
17
17
  from .failure_classification import FailureClassification, classify_provider_failure
18
+ from .provider_contracts import (
19
+ ApprovalContract,
20
+ AuthContract,
21
+ HeadlessContract,
22
+ PathContract,
23
+ PromptPayload,
24
+ PromptTransport,
25
+ ProviderContract,
26
+ SandboxContract,
27
+ build_env_overlay,
28
+ get_provider_contract,
29
+ render_prompt,
30
+ resolve_config_paths,
31
+ )
18
32
  from .provider_controls import build_model_id, resolve_provider_model_controls
19
33
  from .provider_specs import (
20
34
  ChoiceSpec,
@@ -56,6 +70,8 @@ from .session_logs import (
56
70
  from .subprocess_runner import run_cli_command, run_cli_command_sync
57
71
 
58
72
  __all__ = [
73
+ "ApprovalContract",
74
+ "AuthContract",
59
75
  "AuthMode",
60
76
  "AuthResolution",
61
77
  "CliRunRequest",
@@ -67,14 +83,21 @@ __all__ = [
67
83
  "ControlSpec",
68
84
  "ErrorCode",
69
85
  "FailureClassification",
86
+ "HeadlessContract",
70
87
  "ModelSpec",
88
+ "PathContract",
89
+ "PromptPayload",
90
+ "PromptTransport",
91
+ "ProviderContract",
71
92
  "ProviderSpec",
93
+ "SandboxContract",
72
94
  "SchemaValidationError",
73
95
  "InteractiveCliRunResult",
74
96
  "SessionProgressEvent",
75
97
  "SessionRetryDecision",
76
98
  "SessionExecutionTimeoutError",
77
99
  "TranscriptMirrorStrategy",
100
+ "build_env_overlay",
78
101
  "get_claude_default_model",
79
102
  "get_claude_effort_levels",
80
103
  "get_claude_model_candidates",
@@ -85,14 +108,17 @@ __all__ = [
85
108
  "get_copilot_model_catalog",
86
109
  "get_gemini_default_model",
87
110
  "get_gemini_model_options",
111
+ "get_provider_contract",
88
112
  "get_provider_spec",
89
113
  "list_provider_specs",
90
114
  "build_model_id",
91
115
  "build_codex_exec_spec",
92
116
  "classify_provider_failure",
93
117
  "load_schema",
118
+ "render_prompt",
94
119
  "resolve_auth",
95
120
  "resolve_claude_reasoning_policy",
121
+ "resolve_config_paths",
96
122
  "resolve_provider_model_controls",
97
123
  "redact_text",
98
124
  "claude_project_key",
@@ -6,6 +6,7 @@ import os
6
6
  from dataclasses import dataclass
7
7
 
8
8
  from .contracts import AuthMode
9
+ from .provider_contracts import _CONTRACTS
9
10
 
10
11
 
11
12
  @dataclass(frozen=True)
@@ -18,15 +19,30 @@ class AuthResolution:
18
19
  hint: str
19
20
 
20
21
 
21
- _PROVIDER_ENV_HINTS: dict[str, tuple[tuple[str, ...], str]] = {
22
- "claude": (("CLAUDE_API_KEY",), "Use `claude login` or set CLAUDE_API_KEY."),
23
- "copilot": (
24
- tuple(),
25
- "Use `copilot auth login` (CLI login is the default authentication mode).",
26
- ),
27
- "codex": (("OPENAI_API_KEY",), "Use Codex login flow or set OPENAI_API_KEY."),
28
- "gemini": (("GEMINI_API_KEY",), "Use `gemini auth login` or set GEMINI_API_KEY."),
29
- }
22
+ def _build_provider_env_hints() -> dict[str, tuple[tuple[str, ...], str]]:
23
+ """Derive auth env hints from the authoritative provider contracts.
24
+
25
+ Only ``api_key_env_var`` (primary auth) populates ``required_env``.
26
+ ``byok_api_key_env_var`` is an opt-in override and should not appear
27
+ as "required" or "missing" — callers discover BYOK via the contract.
28
+ """
29
+ hints: dict[str, tuple[tuple[str, ...], str]] = {}
30
+ for provider_id, contract in _CONTRACTS.items():
31
+ auth = contract.auth
32
+ env_vars: list[str] = []
33
+ if auth.api_key_env_var:
34
+ env_vars.append(auth.api_key_env_var)
35
+ if env_vars:
36
+ hint = f"Use CLI login or set {env_vars[0]}."
37
+ elif auth.byok_api_key_env_var:
38
+ hint = f"Use CLI login. BYOK available via {auth.byok_api_key_env_var}."
39
+ else:
40
+ hint = "Use CLI login (no env-based auth path available)."
41
+ hints[provider_id] = (tuple(env_vars), hint)
42
+ return hints
43
+
44
+
45
+ _PROVIDER_ENV_HINTS = _build_provider_env_hints()
30
46
 
31
47
 
32
48
  def resolve_auth(provider: str, env: dict[str, str] | None = None) -> AuthResolution: