coding-cli-runtime 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- coding_cli_runtime/__init__.py +12 -2
- coding_cli_runtime/codex_cli.py +10 -8
- coding_cli_runtime/headless.py +124 -0
- coding_cli_runtime/provider_contracts.py +44 -38
- coding_cli_runtime/session_logs.py +56 -0
- {coding_cli_runtime-0.2.0.dist-info → coding_cli_runtime-0.3.0.dist-info}/METADATA +88 -13
- {coding_cli_runtime-0.2.0.dist-info → coding_cli_runtime-0.3.0.dist-info}/RECORD +10 -9
- {coding_cli_runtime-0.2.0.dist-info → coding_cli_runtime-0.3.0.dist-info}/WHEEL +0 -0
- {coding_cli_runtime-0.2.0.dist-info → coding_cli_runtime-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {coding_cli_runtime-0.2.0.dist-info → coding_cli_runtime-0.3.0.dist-info}/top_level.txt +0 -0
coding_cli_runtime/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__version__ = "0.
|
|
5
|
+
__version__ = "0.3.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,12 @@ from .contracts import (
|
|
|
15
15
|
ErrorCode,
|
|
16
16
|
)
|
|
17
17
|
from .failure_classification import FailureClassification, classify_provider_failure
|
|
18
|
+
from .headless import (
|
|
19
|
+
build_claude_headless_core,
|
|
20
|
+
build_codex_headless_core,
|
|
21
|
+
build_copilot_headless_core,
|
|
22
|
+
build_gemini_headless_core,
|
|
23
|
+
)
|
|
18
24
|
from .provider_contracts import (
|
|
19
25
|
ApprovalContract,
|
|
20
26
|
AuthContract,
|
|
@@ -97,7 +103,12 @@ __all__ = [
|
|
|
97
103
|
"SessionRetryDecision",
|
|
98
104
|
"SessionExecutionTimeoutError",
|
|
99
105
|
"TranscriptMirrorStrategy",
|
|
106
|
+
"build_claude_headless_core",
|
|
107
|
+
"build_codex_exec_spec",
|
|
108
|
+
"build_codex_headless_core",
|
|
109
|
+
"build_copilot_headless_core",
|
|
100
110
|
"build_env_overlay",
|
|
111
|
+
"build_gemini_headless_core",
|
|
101
112
|
"get_claude_default_model",
|
|
102
113
|
"get_claude_effort_levels",
|
|
103
114
|
"get_claude_model_candidates",
|
|
@@ -112,7 +123,6 @@ __all__ = [
|
|
|
112
123
|
"get_provider_spec",
|
|
113
124
|
"list_provider_specs",
|
|
114
125
|
"build_model_id",
|
|
115
|
-
"build_codex_exec_spec",
|
|
116
126
|
"classify_provider_failure",
|
|
117
127
|
"load_schema",
|
|
118
128
|
"render_prompt",
|
coding_cli_runtime/codex_cli.py
CHANGED
|
@@ -60,18 +60,20 @@ def build_codex_exec_spec(
|
|
|
60
60
|
model_controls=model_controls,
|
|
61
61
|
)
|
|
62
62
|
reasoning_config_value = json.dumps(effective_reasoning)
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
from .headless import build_codex_headless_core
|
|
65
|
+
|
|
66
|
+
cmd_parts: list[str] = build_codex_headless_core(
|
|
67
|
+
model,
|
|
68
|
+
binary=str(codex_bin),
|
|
69
|
+
sandbox_mode=sandbox if sandbox else None,
|
|
70
|
+
full_auto=full_auto,
|
|
71
|
+
skip_git_repo_check=skip_git_repo_check,
|
|
72
|
+
)
|
|
64
73
|
if json_output:
|
|
65
74
|
cmd_parts.append("--json")
|
|
66
|
-
if full_auto:
|
|
67
|
-
cmd_parts.append("--full-auto")
|
|
68
|
-
cmd_parts.extend(["--sandbox", sandbox])
|
|
69
|
-
if skip_git_repo_check:
|
|
70
|
-
cmd_parts.append("--skip-git-repo-check")
|
|
71
75
|
cmd_parts.extend(
|
|
72
76
|
[
|
|
73
|
-
"--model",
|
|
74
|
-
model,
|
|
75
77
|
"--config",
|
|
76
78
|
f"model_reasoning_effort={reasoning_config_value}",
|
|
77
79
|
"-C",
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Per-provider headless launch core helpers.
|
|
2
|
+
|
|
3
|
+
Each helper emits the standard headless launch args for its provider,
|
|
4
|
+
derived from ``ProviderContract.headless``. Consumers append their own
|
|
5
|
+
app-specific tails (tool restrictions, output paths, prompt, etc.).
|
|
6
|
+
|
|
7
|
+
These helpers are the canonical source for headless launch flag assembly
|
|
8
|
+
within ``coding_cli_runtime``. In-repo consumers (feather, codex_cli,
|
|
9
|
+
provider_contracts builder) delegate to them. App-generation provider
|
|
10
|
+
wrappers may still assemble flags directly when their command construction
|
|
11
|
+
is interleaved with consumer-specific logic (reasoning config, output
|
|
12
|
+
format, artifact paths).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .provider_contracts import get_provider_contract
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_claude_headless_core(
|
|
21
|
+
model: str,
|
|
22
|
+
*,
|
|
23
|
+
binary: str | None = None,
|
|
24
|
+
permission_mode: str | None = None,
|
|
25
|
+
skip_permissions: bool = True,
|
|
26
|
+
) -> list[str]:
|
|
27
|
+
"""Build Claude headless launch core args.
|
|
28
|
+
|
|
29
|
+
Returns args up to and including ``--model``. Does NOT include prompt,
|
|
30
|
+
output format, tool restrictions, or other app-specific flags.
|
|
31
|
+
"""
|
|
32
|
+
contract = get_provider_contract("claude")
|
|
33
|
+
h = contract.headless
|
|
34
|
+
cmd: list[str] = [binary or contract.binary]
|
|
35
|
+
cmd.extend(h.activation_args)
|
|
36
|
+
if h.approval.permission_mode_flag:
|
|
37
|
+
mode = permission_mode or h.approval.default_permission_mode
|
|
38
|
+
if mode:
|
|
39
|
+
cmd.extend([h.approval.permission_mode_flag, mode])
|
|
40
|
+
if skip_permissions and h.approval.flag:
|
|
41
|
+
cmd.append(h.approval.flag)
|
|
42
|
+
cmd.extend(["--model", model])
|
|
43
|
+
return cmd
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_codex_headless_core(
|
|
47
|
+
model: str,
|
|
48
|
+
*,
|
|
49
|
+
binary: str | None = None,
|
|
50
|
+
sandbox_mode: str | None = None,
|
|
51
|
+
full_auto: bool = True,
|
|
52
|
+
skip_git_repo_check: bool = True,
|
|
53
|
+
) -> list[str]:
|
|
54
|
+
"""Build Codex headless launch core args.
|
|
55
|
+
|
|
56
|
+
Returns args including ``exec``, ``--full-auto``, ``--sandbox``,
|
|
57
|
+
``--skip-git-repo-check``, and ``--model``. Does NOT include
|
|
58
|
+
``-C``, ``-o``, ``--output-schema``, or reasoning config.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
full_auto: Include ``--full-auto`` (default True).
|
|
62
|
+
skip_git_repo_check: Include ``--skip-git-repo-check`` (default True).
|
|
63
|
+
"""
|
|
64
|
+
contract = get_provider_contract("codex")
|
|
65
|
+
h = contract.headless
|
|
66
|
+
cmd: list[str] = [binary or contract.binary]
|
|
67
|
+
cmd.extend(h.activation_args)
|
|
68
|
+
if full_auto and h.noninteractive_mode_flag:
|
|
69
|
+
cmd.append(h.noninteractive_mode_flag)
|
|
70
|
+
if h.sandbox is not None:
|
|
71
|
+
mode = sandbox_mode or h.sandbox.writable_mode
|
|
72
|
+
cmd.extend([h.sandbox.flag, mode])
|
|
73
|
+
if skip_git_repo_check and h.requires_git_repo and h.skip_git_repo_flag:
|
|
74
|
+
cmd.append(h.skip_git_repo_flag)
|
|
75
|
+
cmd.extend(["--model", model])
|
|
76
|
+
return cmd
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def build_copilot_headless_core(
|
|
80
|
+
model: str,
|
|
81
|
+
*,
|
|
82
|
+
binary: str | None = None,
|
|
83
|
+
stream: str | None = None,
|
|
84
|
+
) -> list[str]:
|
|
85
|
+
"""Build Copilot headless launch core args.
|
|
86
|
+
|
|
87
|
+
Returns args including activation (``--no-ask-user``,
|
|
88
|
+
``--no-custom-instructions``), ``--allow-all``, ``--stream``,
|
|
89
|
+
and ``--model``. Does NOT include ``-p``, ``--share``, or
|
|
90
|
+
force-implementation.
|
|
91
|
+
"""
|
|
92
|
+
contract = get_provider_contract("copilot")
|
|
93
|
+
h = contract.headless
|
|
94
|
+
cmd: list[str] = [binary or contract.binary]
|
|
95
|
+
cmd.extend(h.activation_args)
|
|
96
|
+
if h.approval.flag:
|
|
97
|
+
cmd.append(h.approval.flag)
|
|
98
|
+
cmd.extend(["--model", model])
|
|
99
|
+
if h.stream_flag:
|
|
100
|
+
stream_value = stream or h.default_stream_mode
|
|
101
|
+
if stream_value:
|
|
102
|
+
cmd.extend([h.stream_flag, stream_value])
|
|
103
|
+
return cmd
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def build_gemini_headless_core(
|
|
107
|
+
model: str,
|
|
108
|
+
*,
|
|
109
|
+
binary: str | None = None,
|
|
110
|
+
) -> list[str]:
|
|
111
|
+
"""Build Gemini headless launch core args.
|
|
112
|
+
|
|
113
|
+
Returns args including approval flag (``--yolo``) and ``--model``.
|
|
114
|
+
Does NOT include ``--prompt ""`` activation (that's part of prompt
|
|
115
|
+
transport, handled by ``render_prompt()``).
|
|
116
|
+
"""
|
|
117
|
+
contract = get_provider_contract("gemini")
|
|
118
|
+
h = contract.headless
|
|
119
|
+
cmd: list[str] = [binary or contract.binary]
|
|
120
|
+
cmd.extend(h.activation_args)
|
|
121
|
+
if h.approval.flag:
|
|
122
|
+
cmd.append(h.approval.flag)
|
|
123
|
+
cmd.extend(["--model", model])
|
|
124
|
+
return cmd
|
|
@@ -406,47 +406,53 @@ def _build_non_interactive_run(
|
|
|
406
406
|
stream: str | None = None,
|
|
407
407
|
extra_flags: tuple[str, ...] = (),
|
|
408
408
|
) -> NonInteractiveRunSpec:
|
|
409
|
-
"""Build a non-interactive CLI run spec. Internal convenience.
|
|
410
|
-
contract = get_provider_contract(provider_id)
|
|
411
|
-
h = contract.headless
|
|
412
|
-
|
|
413
|
-
bin_name = binary or contract.binary
|
|
414
|
-
cmd: list[str] = [bin_name]
|
|
415
|
-
|
|
416
|
-
# Headless activation (e.g. "--print" for Claude, "exec" for Codex)
|
|
417
|
-
cmd.extend(h.activation_args)
|
|
418
|
-
|
|
419
|
-
# Non-interactive mode flag (e.g. "--full-auto" for Codex)
|
|
420
|
-
if h.noninteractive_mode_flag:
|
|
421
|
-
cmd.append(h.noninteractive_mode_flag)
|
|
422
|
-
|
|
423
|
-
# Sandbox (Codex)
|
|
424
|
-
if h.sandbox is not None:
|
|
425
|
-
mode = codex_sandbox_mode or h.sandbox.writable_mode
|
|
426
|
-
cmd.extend([h.sandbox.flag, mode])
|
|
427
|
-
|
|
428
|
-
# Git repo bypass
|
|
429
|
-
if h.requires_git_repo and h.skip_git_repo_flag:
|
|
430
|
-
cmd.append(h.skip_git_repo_flag)
|
|
409
|
+
"""Build a non-interactive CLI run spec. Internal convenience.
|
|
431
410
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
411
|
+
Delegates headless core arg assembly to ``headless.build_*_headless_core()``
|
|
412
|
+
helpers, which derive flags from ``ProviderContract.headless``.
|
|
413
|
+
"""
|
|
414
|
+
from .headless import (
|
|
415
|
+
build_claude_headless_core,
|
|
416
|
+
build_codex_headless_core,
|
|
417
|
+
build_copilot_headless_core,
|
|
418
|
+
build_gemini_headless_core,
|
|
419
|
+
)
|
|
441
420
|
|
|
442
|
-
|
|
443
|
-
|
|
421
|
+
contract = get_provider_contract(provider_id)
|
|
422
|
+
h = contract.headless
|
|
423
|
+
key = provider_id.strip().lower()
|
|
444
424
|
|
|
445
|
-
#
|
|
446
|
-
if
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
425
|
+
# Headless core (binary + activation + approval + model + stream)
|
|
426
|
+
if key == "claude":
|
|
427
|
+
cmd = build_claude_headless_core(model, binary=binary, permission_mode=permission_mode)
|
|
428
|
+
elif key == "codex":
|
|
429
|
+
cmd = build_codex_headless_core(model, binary=binary, sandbox_mode=codex_sandbox_mode)
|
|
430
|
+
elif key == "copilot":
|
|
431
|
+
cmd = build_copilot_headless_core(model, binary=binary, stream=stream)
|
|
432
|
+
elif key == "gemini":
|
|
433
|
+
cmd = build_gemini_headless_core(model, binary=binary)
|
|
434
|
+
else:
|
|
435
|
+
# Fallback for unknown providers — generic assembly
|
|
436
|
+
bin_name = binary or contract.binary
|
|
437
|
+
cmd = [bin_name, *h.activation_args]
|
|
438
|
+
if h.noninteractive_mode_flag:
|
|
439
|
+
cmd.append(h.noninteractive_mode_flag)
|
|
440
|
+
if h.sandbox is not None:
|
|
441
|
+
mode = codex_sandbox_mode or h.sandbox.writable_mode
|
|
442
|
+
cmd.extend([h.sandbox.flag, mode])
|
|
443
|
+
if h.requires_git_repo and h.skip_git_repo_flag:
|
|
444
|
+
cmd.append(h.skip_git_repo_flag)
|
|
445
|
+
if h.approval.flag:
|
|
446
|
+
cmd.append(h.approval.flag)
|
|
447
|
+
if h.approval.permission_mode_flag:
|
|
448
|
+
mode_value = permission_mode or h.approval.default_permission_mode
|
|
449
|
+
if mode_value:
|
|
450
|
+
cmd.extend([h.approval.permission_mode_flag, mode_value])
|
|
451
|
+
cmd.extend(["--model", model])
|
|
452
|
+
if h.stream_flag:
|
|
453
|
+
stream_value = stream or h.default_stream_mode
|
|
454
|
+
if stream_value:
|
|
455
|
+
cmd.extend([h.stream_flag, stream_value])
|
|
450
456
|
|
|
451
457
|
# Prompt
|
|
452
458
|
payload = render_prompt(h.prompt, prompt)
|
|
@@ -5,7 +5,11 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
|
+
from collections.abc import Callable
|
|
8
9
|
from pathlib import Path
|
|
10
|
+
from typing import TypeVar
|
|
11
|
+
|
|
12
|
+
_T = TypeVar("_T")
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
def normalize_path_str(path_str: str) -> str:
|
|
@@ -15,6 +19,58 @@ def normalize_path_str(path_str: str) -> str:
|
|
|
15
19
|
return os.path.normpath(path_str)
|
|
16
20
|
|
|
17
21
|
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Generic session-directory scanning primitive
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def scan_session_dir(
|
|
28
|
+
directory: Path,
|
|
29
|
+
*,
|
|
30
|
+
glob_pattern: str = "*.jsonl",
|
|
31
|
+
since_ts: float,
|
|
32
|
+
mtime_buffer: float = 15.0,
|
|
33
|
+
extract_fn: Callable[[Path], _T | None],
|
|
34
|
+
max_candidates: int = 200,
|
|
35
|
+
) -> list[tuple[float, Path, _T]]:
|
|
36
|
+
"""Scan a directory for session files, filter by mtime, extract metadata.
|
|
37
|
+
|
|
38
|
+
Returns a list of ``(mtime, path, extracted)`` tuples sorted by mtime
|
|
39
|
+
descending. Provider-specific ranking/selection stays with the caller.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
directory: Directory to scan.
|
|
43
|
+
glob_pattern: Glob pattern for session files (default: ``*.jsonl``).
|
|
44
|
+
since_ts: Only include files with mtime >= ``since_ts - mtime_buffer``.
|
|
45
|
+
mtime_buffer: Seconds of slack before ``since_ts`` (default: 15).
|
|
46
|
+
extract_fn: Called on each candidate path. Return ``None`` to skip.
|
|
47
|
+
max_candidates: Max number of candidates to process after mtime filter.
|
|
48
|
+
"""
|
|
49
|
+
if not directory.exists():
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
candidates: list[tuple[float, Path]] = []
|
|
53
|
+
try:
|
|
54
|
+
for path in directory.rglob(glob_pattern):
|
|
55
|
+
try:
|
|
56
|
+
mtime = path.stat().st_mtime
|
|
57
|
+
except OSError:
|
|
58
|
+
continue
|
|
59
|
+
if mtime >= since_ts - mtime_buffer:
|
|
60
|
+
candidates.append((mtime, path))
|
|
61
|
+
except (OSError, RuntimeError):
|
|
62
|
+
return []
|
|
63
|
+
|
|
64
|
+
candidates.sort(key=lambda item: item[0], reverse=True)
|
|
65
|
+
|
|
66
|
+
results: list[tuple[float, Path, _T]] = []
|
|
67
|
+
for mtime, path in candidates[:max_candidates]:
|
|
68
|
+
extracted = extract_fn(path)
|
|
69
|
+
if extracted is not None:
|
|
70
|
+
results.append((mtime, path, extracted))
|
|
71
|
+
return results
|
|
72
|
+
|
|
73
|
+
|
|
18
74
|
def codex_session_roots() -> list[Path]:
|
|
19
75
|
base = Path.home() / ".codex"
|
|
20
76
|
return [base / "sessions", base / "archived_sessions"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-cli-runtime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -40,17 +40,21 @@ code doesn't need provider-specific subprocess handling.
|
|
|
40
40
|
|
|
41
41
|
**What it does (and why not just `subprocess.run`):**
|
|
42
42
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
43
|
+
- Run any provider CLI with unified request/result types and timeout enforcement
|
|
44
|
+
- Query the model catalog (with user-override and live-cache fallback)
|
|
45
|
+
- Classify failures as retryable vs fatal per provider
|
|
46
|
+
- Look up provider auth, config dirs, and headless launch flags
|
|
47
|
+
- Build non-interactive launch commands without hardcoding provider flags
|
|
48
|
+
- Find session logs after a run (Codex, Claude)
|
|
49
|
+
- Run long-lived sessions with process-group cleanup and transcript mirroring
|
|
50
|
+
- No Python package dependencies — only requires the provider CLIs themselves
|
|
49
51
|
|
|
50
52
|
## Installation
|
|
51
53
|
|
|
52
54
|
```bash
|
|
53
55
|
pip install coding-cli-runtime
|
|
56
|
+
# or
|
|
57
|
+
uv add coding-cli-runtime
|
|
54
58
|
```
|
|
55
59
|
|
|
56
60
|
Requires Python 3.10+.
|
|
@@ -65,7 +69,7 @@ from pathlib import Path
|
|
|
65
69
|
from coding_cli_runtime import CliRunRequest, run_cli_command
|
|
66
70
|
|
|
67
71
|
request = CliRunRequest(
|
|
68
|
-
cmd_parts=("codex", "--model", "
|
|
72
|
+
cmd_parts=("codex", "--model", "gpt-5.4", "--quiet", "exec", "fix the tests"),
|
|
69
73
|
cwd=Path("/tmp/my-project"),
|
|
70
74
|
timeout_seconds=120,
|
|
71
75
|
)
|
|
@@ -180,6 +184,38 @@ can drill into whichever aspect they need. This is reference metadata,
|
|
|
180
184
|
not a command-construction control plane — consumers keep their own
|
|
181
185
|
command assembly and adopt contract fields selectively.
|
|
182
186
|
|
|
187
|
+
### Build headless launch commands
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from coding_cli_runtime import build_claude_headless_core, build_codex_headless_core
|
|
191
|
+
|
|
192
|
+
# Claude: binary + --print + --permission-mode + --dangerously-skip-permissions + --model
|
|
193
|
+
cmd = build_claude_headless_core("claude-sonnet-4-6")
|
|
194
|
+
cmd.extend(["--output-format", "text", "--disallowedTools", "Bash,Task"])
|
|
195
|
+
|
|
196
|
+
# Codex: binary + exec + --full-auto + --sandbox + --skip-git-repo-check + --model
|
|
197
|
+
cmd = build_codex_headless_core("gpt-5.4", sandbox_mode="read-only")
|
|
198
|
+
cmd.extend(["-C", str(workdir)])
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Headless core helpers emit the standard flags for non-interactive runs.
|
|
202
|
+
Consumers append app-specific tails (tool restrictions, output paths, etc.).
|
|
203
|
+
|
|
204
|
+
### Find session logs after a run
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
import time
|
|
208
|
+
from coding_cli_runtime import find_codex_session, find_claude_session
|
|
209
|
+
|
|
210
|
+
# Find the most recent Codex session log for a given working directory
|
|
211
|
+
session = find_codex_session("/path/to/project", since_ts=time.time() - 300)
|
|
212
|
+
if session:
|
|
213
|
+
print(f"Session log: {session}") # ~/.codex/sessions/.../conversation.jsonl
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Works for Codex and Claude. Scans provider config directories for session
|
|
217
|
+
files matching the working directory and time window.
|
|
218
|
+
|
|
183
219
|
## Key types
|
|
184
220
|
|
|
185
221
|
| Type | Purpose |
|
|
@@ -191,11 +227,50 @@ command assembly and adopt contract fields selectively.
|
|
|
191
227
|
| `ProviderContract` | Structured provider CLI metadata (auth, paths, headless launch) |
|
|
192
228
|
| `FailureClassification` | Classified error with retryable flag and category |
|
|
193
229
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
230
|
+
### Run long-lived CLI sessions
|
|
231
|
+
|
|
232
|
+
For CLI runs that take minutes (e.g., full app generation), use
|
|
233
|
+
`run_interactive_session()` instead of `run_cli_command()`. It adds:
|
|
234
|
+
|
|
235
|
+
- Process-group cleanup (kills orphaned child processes on timeout)
|
|
236
|
+
- Transcript mirroring (streams CLI output to a file while the process runs)
|
|
237
|
+
- Automatic retries on transient failures
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from coding_cli_runtime import run_interactive_session
|
|
241
|
+
|
|
242
|
+
result = await run_interactive_session(
|
|
243
|
+
cmd_parts=("claude", "--print", "--model", "claude-sonnet-4-6"),
|
|
244
|
+
cwd=workdir,
|
|
245
|
+
stdin_text=prompt,
|
|
246
|
+
logger=logger,
|
|
247
|
+
timeout_seconds=600,
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Only `cmd_parts`, `cwd`, `stdin_text`, and `logger` are required.
|
|
252
|
+
Observability labels (`job_name`, `phase_tag`) default to sensible values.
|
|
253
|
+
|
|
254
|
+
## API summary
|
|
255
|
+
|
|
256
|
+
The full public API is listed in [`__init__.py`](src/coding_cli_runtime/__init__.py).
|
|
257
|
+
Key function groups:
|
|
258
|
+
|
|
259
|
+
| Group | Functions |
|
|
260
|
+
|-------|-----------|
|
|
261
|
+
| Execution | `run_cli_command`, `run_cli_command_sync`, `run_interactive_session` |
|
|
262
|
+
| Provider metadata | `get_provider_contract`, `get_provider_spec`, `list_provider_specs` |
|
|
263
|
+
| Contract helpers | `build_env_overlay`, `resolve_config_paths`, `render_prompt`, `resolve_auth` |
|
|
264
|
+
| Headless launch | `build_claude_headless_core`, `build_codex_headless_core`, `build_copilot_headless_core`, `build_gemini_headless_core` |
|
|
265
|
+
| Codex batch | `build_codex_exec_spec` |
|
|
266
|
+
| Failure handling | `classify_provider_failure` |
|
|
267
|
+
| Session logs | `find_codex_session`, `find_claude_session` |
|
|
268
|
+
| Schema | `load_schema`, `validate_payload` |
|
|
269
|
+
| Utilities | `redact_text`, `build_model_id`, `normalize_path_str` |
|
|
270
|
+
|
|
271
|
+
## Contributing
|
|
272
|
+
|
|
273
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and quality checks.
|
|
199
274
|
|
|
200
275
|
## Prerequisites
|
|
201
276
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
coding_cli_runtime/__init__.py,sha256=
|
|
1
|
+
coding_cli_runtime/__init__.py,sha256=S_dSga-xYos95nGC4yq8gQ2SvAUrpERnAFyJ-_SZDkY,3845
|
|
2
2
|
coding_cli_runtime/auth.py,sha256=XP3TZINazvzKcrdgp-pcJcbG4s220dbVVPjF7ivI-wA,2284
|
|
3
|
-
coding_cli_runtime/codex_cli.py,sha256=
|
|
3
|
+
coding_cli_runtime/codex_cli.py,sha256=h26tfb1Kj9LyQV19OvMV-DS4NKvdWrDPXVKGhS8lALI,3002
|
|
4
4
|
coding_cli_runtime/contracts.py,sha256=teYMPDYCjL6HRwRBucJKetfZKlRnxpG82BrC1Y1OMNg,1764
|
|
5
5
|
coding_cli_runtime/copilot_reasoning_baseline.json,sha256=hEIsqm03-D8T9Snn_FvbC2RD367fXGziyTK0Ajpxrmk,1649
|
|
6
6
|
coding_cli_runtime/copilot_reasoning_logs.py,sha256=S2GD0zGgwVXAPe-DyJPKMR5j-EgOGlANnIt315mIWuo,2327
|
|
7
7
|
coding_cli_runtime/failure_classification.py,sha256=fjGOjtQaBh6Y13gEIS7ystmadcrFKsLAN-fccBuerBs,6027
|
|
8
|
+
coding_cli_runtime/headless.py,sha256=0q0L-crpIRb4A7GCOH2kyoSnyw37USGyFTzP6OOQq9Q,4186
|
|
8
9
|
coding_cli_runtime/json_io.py,sha256=1RseVXV-uPWRm_-pGIUTAvhALnMAEivCX46zvlVpri0,2665
|
|
9
|
-
coding_cli_runtime/provider_contracts.py,sha256=
|
|
10
|
+
coding_cli_runtime/provider_contracts.py,sha256=IgwpuF5_imwdVARzb2azKsitutahlUTN_KWZxn3Kvw0,15422
|
|
10
11
|
coding_cli_runtime/provider_controls.py,sha256=2rl1XxGYFODu6LRx3bLhptgN5M_T3klx2lqT_gAAkb0,3328
|
|
11
12
|
coding_cli_runtime/provider_specs.py,sha256=n9cptkPVI5sIBVuJlqE8nuTU7SyJwG8rkUF2enM_KZc,26777
|
|
12
13
|
coding_cli_runtime/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
@@ -14,12 +15,12 @@ coding_cli_runtime/reasoning.py,sha256=Ggyw1K9Ry4bytzeS-Jy8jmNHVJR891zH_4jRpYAsw
|
|
|
14
15
|
coding_cli_runtime/redaction.py,sha256=PALvJoNt7r0E_Dd3N02tCV9RI_0nPfSgoVAeaWxeLAY,559
|
|
15
16
|
coding_cli_runtime/schema_validation.py,sha256=WZvl2_LkAnuxNMmpS2-vjtLqny034G9xT7wh1BZ1gwM,3929
|
|
16
17
|
coding_cli_runtime/session_execution.py,sha256=U9oRrz2ORuZJzUf4WK2BS6ubCSLOHe94izg6xmn6d3E,20714
|
|
17
|
-
coding_cli_runtime/session_logs.py,sha256=
|
|
18
|
+
coding_cli_runtime/session_logs.py,sha256=wyHld9yVydWd06jSxFSW7MwzxSbiJVLNzKN4Hh6dUkU,5689
|
|
18
19
|
coding_cli_runtime/subprocess_runner.py,sha256=WqYMI6ALWFhEUySycfHXEtXuCX3m5x7s1n3Bd0TyPm0,11419
|
|
19
20
|
coding_cli_runtime/schemas/normalized_run_result.v1.json,sha256=ogVKJbDFAd9dJklmp8SUkdR9L5EX1rdHGj5leJJHXGs,1110
|
|
20
21
|
coding_cli_runtime/schemas/reasoning_metadata.v1.json,sha256=nQWhqp9-dlzJM18OARDUwAyaA-3-I8rETZYvkgTAnOc,467
|
|
21
|
-
coding_cli_runtime-0.
|
|
22
|
-
coding_cli_runtime-0.
|
|
23
|
-
coding_cli_runtime-0.
|
|
24
|
-
coding_cli_runtime-0.
|
|
25
|
-
coding_cli_runtime-0.
|
|
22
|
+
coding_cli_runtime-0.3.0.dist-info/licenses/LICENSE,sha256=hVIuaMVAgQkhTh44et0cpDtN3kGOZnKQ2bY1rJJw-MI,1078
|
|
23
|
+
coding_cli_runtime-0.3.0.dist-info/METADATA,sha256=e9518dIGTaGLay8wou9xEF2V1tjsqqPsZghRLSjIvKs,10801
|
|
24
|
+
coding_cli_runtime-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
25
|
+
coding_cli_runtime-0.3.0.dist-info/top_level.txt,sha256=-tzjii3Qf_GTevxT5M46tITBY02R-K8Ew04hJRHOB2Y,19
|
|
26
|
+
coding_cli_runtime-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|