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.
- coding_agent_cli/__init__.py +47 -0
- coding_agent_cli/_args.py +66 -0
- coding_agent_cli/_classify.py +170 -0
- coding_agent_cli/_drivers/__init__.py +1 -0
- coding_agent_cli/_drivers/_common.py +330 -0
- coding_agent_cli/_drivers/_usage.py +77 -0
- coding_agent_cli/_drivers/agy.py +340 -0
- coding_agent_cli/_drivers/claude.py +304 -0
- coding_agent_cli/_drivers/codex.py +333 -0
- coding_agent_cli/_drivers/codex_rollout.py +439 -0
- coding_agent_cli/_drivers/pi.py +333 -0
- coding_agent_cli/_platform.py +171 -0
- coding_agent_cli/_process.py +338 -0
- coding_agent_cli/api.py +190 -0
- coding_agent_cli/events.py +109 -0
- coding_agent_cli/py.typed +0 -0
- coding_agent_cli/registry.py +67 -0
- coding_agent_cli/types.py +90 -0
- coding_agent_cli-0.1.0.dist-info/METADATA +217 -0
- coding_agent_cli-0.1.0.dist-info/RECORD +22 -0
- coding_agent_cli-0.1.0.dist-info/WHEEL +4 -0
- coding_agent_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -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
|
+
[](https://github.com/sitien173/coding-agent-cli/actions/workflows/ci.yml)
|
|
27
|
+
[](https://pypi.org/project/coding-agent-cli/)
|
|
28
|
+
[](https://pypi.org/project/coding-agent-cli/)
|
|
29
|
+
[](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,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.
|