coding-agent-cli 0.1.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.
@@ -0,0 +1,90 @@
1
+ """Public value types: requests, results, usage, diagnostics, and capabilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass, field
8
+ from enum import StrEnum
9
+ from pathlib import Path
10
+ from types import MappingProxyType
11
+
12
+
13
+ class Outcome(StrEnum):
14
+ """How a run ended. ``retryable`` means possibly transient, not safe to replay."""
15
+
16
+ SUCCESS = "success"
17
+ RETRYABLE = "retryable"
18
+ AGENT_FATAL = "agent_fatal"
19
+ REQUEST_FATAL = "request_fatal"
20
+ CANCELLED = "cancelled"
21
+
22
+
23
+ @dataclass(frozen=True, slots=True, kw_only=True)
24
+ class Diagnostic:
25
+ code: str
26
+ message: str
27
+
28
+
29
+ @dataclass(frozen=True, slots=True, kw_only=True)
30
+ class Usage:
31
+ """Token usage for one invocation. Unknown values are ``None``, never zero.
32
+
33
+ ``input`` includes cache reads and writes once.
34
+ """
35
+
36
+ input: int | None = None
37
+ output: int | None = None
38
+ cache_read: int | None = None
39
+ cache_write: int | None = None
40
+ context_used: int | None = None
41
+ context_window: int | None = None
42
+
43
+
44
+ @dataclass(frozen=True, slots=True, kw_only=True)
45
+ class Request:
46
+ """One prompt for one driver. ``env`` overlays the inherited environment."""
47
+
48
+ driver: str
49
+ prompt: str
50
+ cwd: Path
51
+ session_id: str | None = None
52
+ model: str | None = None
53
+ reasoning: str | None = None
54
+ system_prompt: str | None = None
55
+ args: tuple[str, ...] = ()
56
+ env: Mapping[str, str] = field(default_factory=lambda: MappingProxyType({}))
57
+ timeout_s: float | None = None
58
+ executable: str | None = None
59
+
60
+ def __post_init__(self) -> None:
61
+ object.__setattr__(self, "cwd", Path(self.cwd))
62
+ object.__setattr__(self, "args", tuple(self.args))
63
+ object.__setattr__(self, "env", MappingProxyType(dict(self.env)))
64
+ if self.timeout_s is not None:
65
+ if isinstance(self.timeout_s, bool) or not math.isfinite(self.timeout_s) or self.timeout_s <= 0:
66
+ raise ValueError("timeout_s must be None or a finite positive number")
67
+
68
+
69
+ @dataclass(frozen=True, slots=True, kw_only=True)
70
+ class Result:
71
+ outcome: Outcome
72
+ text: str = ""
73
+ session_id: str | None = None
74
+ model: str | None = None
75
+ usage: Usage = Usage()
76
+ returncode: int | None = None
77
+ error: Diagnostic | None = None
78
+ warnings: tuple[Diagnostic, ...] = ()
79
+
80
+
81
+ @dataclass(frozen=True, slots=True, kw_only=True)
82
+ class Capabilities:
83
+ """What a driver can do on this machine. ``None`` means unverified."""
84
+
85
+ available: bool
86
+ executable: Path | None = None
87
+ structured_streaming: bool | None = None
88
+
89
+
90
+ __all__ = ["Capabilities", "Diagnostic", "Outcome", "Request", "Result", "Usage"]
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.5
2
+ Name: coding-agent-cli
3
+ Version: 0.1.0
4
+ Summary: Run coding-agent CLIs from Python, stream typed events, and get a classified result.
5
+ Project-URL: Homepage, https://github.com/sitien173/coding-agent-cli
6
+ Project-URL: Issues, https://github.com/sitien173/coding-agent-cli/issues
7
+ Project-URL: Changelog, https://github.com/sitien173/coding-agent-cli/blob/main/CHANGELOG.md
8
+ Author: sitien173
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: asyncio,claude,cli,codex,coding-agent,subprocess
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+
24
+ # coding-agent-cli
25
+
26
+ [![CI](https://github.com/sitien173/coding-agent-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/sitien173/coding-agent-cli/actions/workflows/ci.yml)
27
+ [![PyPI](https://img.shields.io/pypi/v/coding-agent-cli)](https://pypi.org/project/coding-agent-cli/)
28
+ [![Python](https://img.shields.io/pypi/pyversions/coding-agent-cli)](https://pypi.org/project/coding-agent-cli/)
29
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
30
+
31
+ Run coding-agent CLIs from Python, stream typed events, and get a classified result.
32
+
33
+ Built-in drivers: `claude`, `codex`, `pi`, and `agy`. Python 3.12 or later on Linux, macOS, and Windows. No runtime dependencies.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install coding-agent-cli
39
+ ```
40
+
41
+ The CLIs themselves are not bundled. Install and log in to each one you use.
42
+
43
+ ## Quick start
44
+
45
+ ```python
46
+ import asyncio
47
+ from pathlib import Path
48
+
49
+ from coding_agent_cli import Request, run
50
+
51
+
52
+ async def main() -> None:
53
+ result = await run(Request(driver="claude", prompt="Summarize README.md", cwd=Path.cwd()))
54
+ print(result.outcome, result.text)
55
+
56
+
57
+ asyncio.run(main())
58
+ ```
59
+
60
+ ### Streaming events
61
+
62
+ Use `stream` inside `async with`. Leaving the block, by `break` or by an exception, cancels the run and kills the process tree.
63
+
64
+ ```python
65
+ from coding_agent_cli import RunCompleted, TextDelta, stream
66
+
67
+
68
+ async def main() -> None:
69
+ async with stream(Request(driver="codex", prompt="List the tests", cwd=Path.cwd())) as events:
70
+ async for event in events:
71
+ if isinstance(event, TextDelta):
72
+ print(event.text, end="")
73
+ elif isinstance(event, RunCompleted):
74
+ print("\n", event.result.outcome)
75
+ ```
76
+
77
+ `run` also accepts `on_event`, an async callback that receives the same events. An exception raised by `on_event` is raised from `run` after the process tree is killed.
78
+
79
+ ### Synchronous use
80
+
81
+ ```python
82
+ from coding_agent_cli import run_sync
83
+
84
+ result = run_sync(Request(driver="pi", prompt="Hello", cwd=Path.cwd()))
85
+ ```
86
+
87
+ `run_sync` raises `RuntimeError` when called inside a running event loop. Await `run` there instead.
88
+
89
+ ### Checking a CLI
90
+
91
+ ```python
92
+ from coding_agent_cli import check
93
+
94
+ caps = await check(Request(driver="agy", prompt="", cwd=Path.cwd()))
95
+ print(caps.available, caps.executable, caps.structured_streaming)
96
+ ```
97
+
98
+ ## Request
99
+
100
+ | Field | Meaning |
101
+ |---|---|
102
+ | `driver` | Driver name, such as `claude`. |
103
+ | `prompt` | The prompt text. |
104
+ | `cwd` | Working directory for the CLI. |
105
+ | `session_id` | Resume this CLI session. |
106
+ | `model` | Portable model option. |
107
+ | `reasoning` | Portable reasoning or effort option. |
108
+ | `system_prompt` | Portable system prompt option. |
109
+ | `args` | Extra CLI arguments, appended by the driver. |
110
+ | `env` | Variables layered over the inherited environment. |
111
+ | `timeout_s` | Wall-clock limit in seconds. |
112
+ | `executable` | Path or name of the CLI to run instead of the default. |
113
+
114
+ ## Portable options
115
+
116
+ | Option | claude | codex | pi | agy |
117
+ |---|---|---|---|---|
118
+ | `model` | `--model` | `--model` | `--model` | `--model` |
119
+ | `reasoning` | `--effort` | `-c model_reasoning_effort=<value>` | `--thinking` | unsupported |
120
+ | `system_prompt` | `--system-prompt` | unsupported | `--system-prompt` | unsupported |
121
+
122
+ An unsupported option returns a `request_fatal` result with code `unsupported_option`. Setting a portable option and the same flag in `args` returns `invalid_args`. So does passing a flag the driver owns for its transport, such as `--output-format`.
123
+
124
+ ## Permission and approval flags
125
+
126
+ The drivers never add a permission, approval, or sandbox-bypass flag. A run that needs tools without prompts must pass the flag through `args`:
127
+
128
+ | CLI | Flag for unattended runs | Notes |
129
+ |---|---|---|
130
+ | claude | `--permission-mode bypassPermissions` | `--dangerously-skip-permissions` is equivalent. |
131
+ | codex | `--dangerously-bypass-approvals-and-sandbox` | Also accepted as `--yolo`. Use only inside an external sandbox. |
132
+ | pi | none | `--approve` trusts project-local files for the run. It is not a tool-permission bypass. |
133
+ | agy | `--dangerously-skip-permissions` | Auto-approves all tool permission requests. |
134
+
135
+ ```python
136
+ Request(driver="codex", prompt="Fix the failing test", cwd=repo, args=("--dangerously-bypass-approvals-and-sandbox",))
137
+ ```
138
+
139
+ These flags give the agent full control of the machine it runs on. Use them only in an environment you are prepared to lose.
140
+
141
+ ## Events
142
+
143
+ Every event has a `kind`, a `sequence` that strictly increases within a run, an optional `entity_id`, and a `schema_version`. Each run ends with exactly one `RunCompleted`, sent after the process tree is gone.
144
+
145
+ | Class | `kind` | Fields |
146
+ |---|---|---|
147
+ | `SessionIdentified` | `session.identified` | `session_id` |
148
+ | `MessageStarted` | `assistant.message.started` | none |
149
+ | `TextDelta` | `assistant.text.delta` | `text` |
150
+ | `MessageCompleted` | `assistant.message.completed` | none |
151
+ | `ToolStarted` | `tool.started` | `tool`, `activity`, `input` |
152
+ | `ToolCompleted` | `tool.completed` | `status` |
153
+ | `UsageUpdated` | `usage.updated` | `usage` |
154
+ | `RunCompleted` | `run.completed` | `result` |
155
+
156
+ ## Outcomes
157
+
158
+ | `Outcome` | Meaning |
159
+ |---|---|
160
+ | `success` | The CLI finished and returned text. |
161
+ | `retryable` | Possibly transient: timeout, service error, nonzero exit, or no output. |
162
+ | `agent_fatal` | The CLI cannot run here: missing CLI, spawn failure, or authentication. |
163
+ | `request_fatal` | The request itself is wrong: bad arguments, unsupported option, invalid model, or context overflow. |
164
+ | `cancelled` | The caller cancelled the run. |
165
+
166
+ `Result.error` holds a `Diagnostic` with a `code` and `message`. `Result.warnings` holds non-fatal diagnostics.
167
+
168
+ **Retryable is not replay-safe.** A `retryable` run may already have edited files, run commands, or spent tokens. Check the working tree before you send the same prompt again.
169
+
170
+ ## Cancellation
171
+
172
+ Pass an `asyncio.Event` as `cancel`. Setting it ends the run with a `cancelled` result that keeps any partial text. From another thread, set it through the loop:
173
+
174
+ ```python
175
+ cancel = asyncio.Event()
176
+ loop = asyncio.get_running_loop()
177
+ # in the other thread:
178
+ loop.call_soon_threadsafe(cancel.set)
179
+ ```
180
+
181
+ ## Custom drivers
182
+
183
+ A driver implements the `Driver` protocol: `async check(req, *, refresh=False) -> Capabilities` and `async run(req, *, on_event, cancel, on_spawn) -> Result`. Register it in a `DriverRegistry` and pass the registry to any entry point:
184
+
185
+ ```python
186
+ from coding_agent_cli import DriverRegistry
187
+
188
+ registry = DriverRegistry()
189
+ registry.register("my-cli", MyDriver())
190
+ result = await run(Request(driver="my-cli", prompt="Hi", cwd=Path.cwd()), registry=registry)
191
+ ```
192
+
193
+ `DriverRegistry(builtins=False)` starts empty. An unknown driver name raises `KeyError`.
194
+
195
+ ## Known limits
196
+
197
+ - **Windows process trees.** Cleanup uses `taskkill /T /F`. A descendant whose launcher exits first can be missed. Job Objects are not used in 0.1.
198
+ - **Live behavior.** The test suite uses fake CLIs. The live tests in `tests/test_live.py` run the real CLIs and are excluded by default: `uv run pytest -m live -rP`.
199
+
200
+ ## Contributing
201
+
202
+ Contributions are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for setup, the coding standard, and the pull request process. Report security problems privately as described in [SECURITY.md](SECURITY.md). Everyone taking part follows the [Code of Conduct](CODE_OF_CONDUCT.md).
203
+
204
+ ```bash
205
+ uv sync
206
+ uv run ruff format --check && uv run ruff check
207
+ uv run pyright
208
+ uv run pytest
209
+ ```
210
+
211
+ ## License
212
+
213
+ [MIT](LICENSE)
214
+
215
+ ## Provenance
216
+
217
+ This library is extracted from the CLI-driver layer of [Refract](https://github.com/sitien173/refract), a multi-model coordination tool. Its drivers, process handling, and many of its tests are ported from Refract under the MIT license.
@@ -0,0 +1,22 @@
1
+ coding_agent_cli/__init__.py,sha256=pJOCmzDrPkmYoPQ3kOAL6dvW2LqkQmacadBF1eQ7zh0,984
2
+ coding_agent_cli/_args.py,sha256=tvhtVoSXxEUETHU15NXYa0bdeMN8A7ATiTzwPsgpj6E,2658
3
+ coding_agent_cli/_classify.py,sha256=XDfAbffiUxiil7hKsxRag4NZDOmVLbqv8fYujGKcBpY,5968
4
+ coding_agent_cli/_platform.py,sha256=XDLr8fdWwhIUOjPL-3NUSoxtGKofftIpbx2IkEURaXE,5853
5
+ coding_agent_cli/_process.py,sha256=2qxwlXxS4aMo_ZhkLAcCM70EUsE1u-twkTn53aHCnNk,12135
6
+ coding_agent_cli/api.py,sha256=eGRlWhhYySw4mNcuQHQ1uZ-BNuEbcshr6GpGklJF9MQ,6461
7
+ coding_agent_cli/events.py,sha256=5PtWqj86L6BYjUWAeo-gM-UdERR274QzITzJ9w8Wq6c,2637
8
+ coding_agent_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ coding_agent_cli/registry.py,sha256=f4ATw4FyXUlFlQS1Czdus7hctuxXq46PWwvGLpbUEQM,2146
10
+ coding_agent_cli/types.py,sha256=frJle_B0eNIwA9N96KO63p9K0ZTBLmolo-i5AaWnhWA,2677
11
+ coding_agent_cli/_drivers/__init__.py,sha256=HbBnDjil7mpdJneUR6s2H-YkeHaf4byOCoSO_yP5Qmg,71
12
+ coding_agent_cli/_drivers/_common.py,sha256=ExMTkqK0dhSD3wxKRST9XbpJ7_Cd_VG0BvM58wIDbIY,11224
13
+ coding_agent_cli/_drivers/_usage.py,sha256=AIPCXjLVRIOF68L810mWLGg_-BYGTIAQrSuuZIWS-fg,2565
14
+ coding_agent_cli/_drivers/agy.py,sha256=cW0cyF6Ln1c3thzpoI_umY0cr47tcT5SNPC5Alpl3bc,13249
15
+ coding_agent_cli/_drivers/claude.py,sha256=CJmcyBbagmNc2WW2nU6MuG_tB4EMa7xEVf0qom9CW1Q,11183
16
+ coding_agent_cli/_drivers/codex.py,sha256=SBWz9ZDcJLBDyCF8fB7K56V2fKQtgo_RC-i91pX60Yo,13071
17
+ coding_agent_cli/_drivers/codex_rollout.py,sha256=kiAmJj638SKi3C7q_jHI3qWFoC95pjSgetiWTuCSZWM,16015
18
+ coding_agent_cli/_drivers/pi.py,sha256=CcTNnLLVisI60OJds87aPKcuQyLlHgEHuFTif71iljY,12279
19
+ coding_agent_cli-0.1.0.dist-info/METADATA,sha256=c5FJ7TDI228jkt7_svawoYPrlkOwV7S3jNP4cIz_Ch0,8720
20
+ coding_agent_cli-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
21
+ coding_agent_cli-0.1.0.dist-info/licenses/LICENSE,sha256=QVZC8PROzGZpwHMdk5lfzO7ZKkamSU7ktYCKw9fOPG4,1066
22
+ coding_agent_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sitien173
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.