codetool-explore 0.5.0__py3-none-win_amd64.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.
- codetool_explore/__init__.py +35 -0
- codetool_explore/_bin/codetool-explore-rust-windows-x86_64.exe +0 -0
- codetool_explore/api.py +266 -0
- codetool_explore/cli.py +188 -0
- codetool_explore/compression.py +150 -0
- codetool_explore/cursor.py +71 -0
- codetool_explore/errors.py +23 -0
- codetool_explore/explorer.py +497 -0
- codetool_explore/ignore.py +222 -0
- codetool_explore/py.typed +0 -0
- codetool_explore/python_backend/__init__.py +154 -0
- codetool_explore/python_backend/case.py +19 -0
- codetool_explore/python_backend/config.py +35 -0
- codetool_explore/python_backend/constants.py +39 -0
- codetool_explore/python_backend/file_search.py +51 -0
- codetool_explore/python_backend/ignore_rules.py +40 -0
- codetool_explore/python_backend/literal.py +79 -0
- codetool_explore/python_backend/matcher.py +79 -0
- codetool_explore/python_backend/models.py +49 -0
- codetool_explore/python_backend/output.py +82 -0
- codetool_explore/python_backend/regex_search.py +63 -0
- codetool_explore/python_backend/search.py +327 -0
- codetool_explore/python_backend/text.py +39 -0
- codetool_explore/python_backend/walker.py +119 -0
- codetool_explore/ranking.py +384 -0
- codetool_explore/roots.py +148 -0
- codetool_explore/rust_backend.py +308 -0
- codetool_explore/text_output.py +475 -0
- codetool_explore-0.5.0.dist-info/METADATA +240 -0
- codetool_explore-0.5.0.dist-info/RECORD +33 -0
- codetool_explore-0.5.0.dist-info/WHEEL +4 -0
- codetool_explore-0.5.0.dist-info/entry_points.txt +2 -0
- codetool_explore-0.5.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codetool-explore
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Fast, dependency-free workspace search, read, and list exploration for coding-agent tools with Rust backend
|
|
5
|
+
Project-URL: Homepage, https://github.com/pbi-agent/codetool-explore
|
|
6
|
+
Project-URL: Repository, https://github.com/pbi-agent/codetool-explore
|
|
7
|
+
Project-URL: Issues, https://github.com/pbi-agent/codetool-explore/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/pbi-agent/codetool-explore/releases
|
|
9
|
+
Author-email: drod <naceur.bs@gmail.com>
|
|
10
|
+
Maintainer-email: drod <naceur.bs@gmail.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agent,code-search,developer-tools,explore,file-search,filesystem,rust,search,text-search
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Rust
|
|
24
|
+
Classifier: Topic :: Software Development
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.12
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# codetool-explore
|
|
32
|
+
|
|
33
|
+
`codetool-explore` is a workspace exploration library built for coding-agent harnesses: fast content search, fast filename/path discovery, read-only file viewing, compact structured results, and predictable token usage.
|
|
34
|
+
|
|
35
|
+
- **Agent-first API**: one public `explore()` call with `target="content"`, `"path"`, `"content_or_path"`, `"read"`, or `"list"`.
|
|
36
|
+
- **Performance-oriented**: dependency-free Python fallback plus optional Rust CLI acceleration for literal and regex content/path search.
|
|
37
|
+
- **Token-compressed output**: compact result keys by default for search, tree-compressed text by default for list, plain text by default for read, `result_format="text"` for raw RTK-style text, and `result_format="full"` for the uncompressed backend shape.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from codetool_explore import explore
|
|
41
|
+
|
|
42
|
+
content = explore("UserService", root=".", mode="files")
|
|
43
|
+
paths = explore("service", root=".", target="path", glob="*.py")
|
|
44
|
+
mixed = explore("UserService", root=".", target="content_or_path")
|
|
45
|
+
scoped = explore("search_workspace", root=["src", "webapp", "tests"], regex=False)
|
|
46
|
+
snippet = explore("README.md", root=".", target="read", start_line=20, limit=40)
|
|
47
|
+
listing = explore("src", root=".", target="list", limit=100)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Patterns are regexes by default, so alternation works without extra flags:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
explore("Maximum number of results|Text or regex pattern", root="tests")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Pass `regex=False` for exact literal matching.
|
|
57
|
+
|
|
58
|
+
For maximum token compression, request raw text:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
print(explore("UserService", root=".", regex=False, result_format="text"))
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Raw text omits backend/totals metadata, groups repeated path prefixes in a small
|
|
65
|
+
tree, crops long snippets/context aggressively, and prints `No Match` for empty
|
|
66
|
+
results. It includes a compact pagination header only when another page exists:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
-- more: cursor=50
|
|
70
|
+
src/
|
|
71
|
+
a.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Raw mode grammar:
|
|
75
|
+
|
|
76
|
+
- `mode="files"`: matching filenames only.
|
|
77
|
+
- `mode="count"`: `path xN`, where `N` is the per-file count.
|
|
78
|
+
- `mode="snippets"`: `path:line:text` without context, or tree-grouped files
|
|
79
|
+
where `line:text` marks a match and other indented text is surrounding context.
|
|
80
|
+
With `target="content_or_path"`, path-only matches are returned as filename rows.
|
|
81
|
+
|
|
82
|
+
## API
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
explore(
|
|
86
|
+
pattern,
|
|
87
|
+
root=".", # path, file, or non-empty list/tuple of paths
|
|
88
|
+
target="content", # "content", "path", "content_or_path", "read", or "list"
|
|
89
|
+
regex=True, # set False for literal search
|
|
90
|
+
path_scope="path", # "path" or "basename" for path matching
|
|
91
|
+
glob=None,
|
|
92
|
+
exclude=None,
|
|
93
|
+
case="smart",
|
|
94
|
+
mode="files", # "files", "snippets", or "count"
|
|
95
|
+
context_lines=0,
|
|
96
|
+
limit=50,
|
|
97
|
+
cursor=None,
|
|
98
|
+
start_line=1, # first line for target="read"
|
|
99
|
+
backend="auto", # "auto", "python", "rust"/"native"
|
|
100
|
+
result_format=None, # default compressed for search, text for read, tree text for list
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`target="content"` searches file contents. `target="path"` searches relative
|
|
105
|
+
file paths without opening file contents. `target="content_or_path"` returns
|
|
106
|
+
files matching either target and marks each row with its match kind.
|
|
107
|
+
`mode="snippets"` supports `target="content"` and `target="content_or_path"`;
|
|
108
|
+
path-only rows under `target="content_or_path"` are returned without
|
|
109
|
+
line/snippet fields.
|
|
110
|
+
|
|
111
|
+
`target="read"` treats `pattern` as one known file path, resolves relative paths
|
|
112
|
+
under a single `root`, and returns plain text with no line-number prefixes.
|
|
113
|
+
Use `start_line` and `limit` to cap the returned line range; if more lines
|
|
114
|
+
remain, text output starts with `-- more: cursor=N`. CSV files are read as
|
|
115
|
+
ordinary text. Binary-looking, missing, unreadable, or directory paths fail with
|
|
116
|
+
controlled `ExploreError` subclasses.
|
|
117
|
+
|
|
118
|
+
`target="list"` treats `pattern` as one file/directory path and returns one
|
|
119
|
+
directory level. Text output uses the same compact tree display as raw search
|
|
120
|
+
output when that saves tokens. Directories end with `/`; file paths are returned
|
|
121
|
+
as one entry. It honors `glob`, `exclude`, ignore files, `limit`, and `cursor`.
|
|
122
|
+
Read/list use the pure-Python stdlib implementation even when `backend="auto"`
|
|
123
|
+
or `"rust"` is requested.
|
|
124
|
+
|
|
125
|
+
`backend="auto"` uses the Rust helper when present, then falls back to pure Python. Regex searches use Rust when supported by its regex engine and fall back to Python for compatibility, including Python `re.finditer` counts for patterns that can match empty spans.
|
|
126
|
+
|
|
127
|
+
`root` accepts `str | os.PathLike | Sequence[str | os.PathLike]`. It may be a
|
|
128
|
+
workspace directory, a single file, or a non-empty list/tuple of directories and
|
|
129
|
+
files:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
explore("search_workspace", root=["src", "webapp", "tests"], regex=False)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
When calling through JSON/tool schemas, pass multi-root values as a JSON array,
|
|
136
|
+
for example `"root": ["src", "webapp", "tests"]`. For resilience with coding
|
|
137
|
+
agents, a space-delimited string such as `"root": "src webapp tests"` is also
|
|
138
|
+
treated as multiple roots when that exact path does not exist and every split
|
|
139
|
+
token is an existing file or directory. Existing paths with spaces still take
|
|
140
|
+
priority; quote individual spaced paths if combining them in one string.
|
|
141
|
+
|
|
142
|
+
File roots search only that file and report paths relative to the file's parent
|
|
143
|
+
directory. Multi-root searches report paths relative to the roots' common base,
|
|
144
|
+
so sibling roots keep prefixes such as `src/...` and `tests/...`; this also lets
|
|
145
|
+
`exclude=["src/generated/**"]` target one root.
|
|
146
|
+
|
|
147
|
+
Controlled failures raise `ExploreError` subclasses:
|
|
148
|
+
|
|
149
|
+
- `ExploreArgumentError` for invalid arguments.
|
|
150
|
+
- `ExplorePatternError` for invalid/unsupported patterns.
|
|
151
|
+
- `ExploreRootError` for missing or unsearchable roots.
|
|
152
|
+
- `ExploreBackendError` for backend runtime failures.
|
|
153
|
+
|
|
154
|
+
## CLI
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
codetool-explore "UserService" . --literal --format text
|
|
158
|
+
codetool-explore "service" . --target path --literal
|
|
159
|
+
codetool-explore "User(Service|Repository)" --root src --mode snippets --raw
|
|
160
|
+
codetool-explore "search_workspace" --root src --root webapp --root tests --literal
|
|
161
|
+
codetool-explore --read README.md --start-line 20 --limit 40
|
|
162
|
+
codetool-explore --list src --glob "*.py"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The CLI defaults to compact JSON for search, plain text for `--read`, and
|
|
166
|
+
tree-compressed text for `--list`.
|
|
167
|
+
Use `--format text` or `--raw` for raw search text; no search matches print
|
|
168
|
+
`No Match`. Repeat `--root` for multiple search roots; read/list accept a single
|
|
169
|
+
root only. A single quoted space-delimited `--root` is accepted as a compatibility
|
|
170
|
+
fallback when it can be split into existing roots.
|
|
171
|
+
|
|
172
|
+
## Install
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uv install codetool-explore
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Wheels can include a platform-specific Rust helper. Without it, the package still works through the Python stdlib backend.
|
|
179
|
+
|
|
180
|
+
## Benchmarks
|
|
181
|
+
|
|
182
|
+
Reproduce and refresh the generated README data:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
cargo build --release --manifest-path rust/Cargo.toml
|
|
186
|
+
uv run python benchmarks/benchmark_search.py \
|
|
187
|
+
--output reports/search_benchmark.json \
|
|
188
|
+
--update-readme
|
|
189
|
+
uv run python benchmarks/benchmark_output_lengths.py \
|
|
190
|
+
--output reports/rtk_vs_codetool_output_lengths.json
|
|
191
|
+
uv run python scripts/update_readme_benchmarks.py \
|
|
192
|
+
--performance reports/search_benchmark.json \
|
|
193
|
+
--tokens reports/rtk_vs_codetool_output_lengths.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
<!-- benchmark-results:start -->
|
|
197
|
+
|
|
198
|
+
<!-- Generated by scripts/update_readme_benchmarks.py; do not edit by hand. -->
|
|
199
|
+
|
|
200
|
+
### Execution performance
|
|
201
|
+
|
|
202
|
+
Mean of median wall-clock timings across 5 corpora × 7 scenarios, 5 measured rounds after 1 warmup.
|
|
203
|
+
|
|
204
|
+
| Tool | Mean median time | Chart |
|
|
205
|
+
| --- | ---: | --- |
|
|
206
|
+
| `codetool-explore` | 127.0 ms | ███████████░░░░░░░ |
|
|
207
|
+
| `rg` | 138.2 ms | ████████████░░░░░░ |
|
|
208
|
+
| `rtk` | 199.7 ms | ██████████████████ |
|
|
209
|
+
|
|
210
|
+
`codetool-explore` is the fastest tool in this run.
|
|
211
|
+
|
|
212
|
+
Source: `reports/search_benchmark.json`.
|
|
213
|
+
|
|
214
|
+
### Token compression
|
|
215
|
+
|
|
216
|
+
Token counts use `tiktoken` when available. The table compares output across 7 RTK-corpus scenarios.
|
|
217
|
+
|
|
218
|
+
| Output | Tokens | Bytes | Chart |
|
|
219
|
+
| --- | ---: | ---: | --- |
|
|
220
|
+
| `explore(..., result_format="text")` | 11,008 | 34.3 KB | ██░░░░░░░░░░░░░░░░ |
|
|
221
|
+
| `rtk grep` stdout | 19,646 | 60.1 KB | ███░░░░░░░░░░░░░░░ |
|
|
222
|
+
| default `explore(...)` | 38,393 | 125.3 KB | █████░░░░░░░░░░░░░ |
|
|
223
|
+
| `explore(..., result_format="full")` | 39,027 | 134.7 KB | █████░░░░░░░░░░░░░ |
|
|
224
|
+
| `rg` stdout | 129,775 | 402.4 KB | ██████████████████ |
|
|
225
|
+
|
|
226
|
+
Default structured output is 7.03% smaller than the full structured shape. Raw text omits backend/totals metadata, includes only a cursor hint when truncated, and prints `No Match` for empty pages. Raw text is 0.56× the `rtk grep` token count in this run.
|
|
227
|
+
|
|
228
|
+
Source: `reports/rtk_vs_codetool_output_lengths.json`.
|
|
229
|
+
|
|
230
|
+
<!-- benchmark-results:end -->
|
|
231
|
+
|
|
232
|
+
## Development
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
uv run pytest
|
|
236
|
+
uv run python scripts/package_rust_binary.py
|
|
237
|
+
uv build --wheel
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Release wheels are built in CI with the staged Rust helper for each target platform.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
codetool_explore/__init__.py,sha256=4eTGfxKeHutGn4vWnKYdwzHF3YNuI2KMlAxAiGQwW4I,1016
|
|
2
|
+
codetool_explore/api.py,sha256=vTmT-ft_-swP-6Bp9ZI6idA8J6bzyt6mxtYzBdp1oXk,9625
|
|
3
|
+
codetool_explore/cli.py,sha256=fY5UhTtZH2sS5_BPX_AgAuQBEyHOemylSfJMs7pyAj0,6061
|
|
4
|
+
codetool_explore/compression.py,sha256=gB22Ofv9fvsiwjwNnRdOxnm4wBrp-_iHIcSspIEtQXI,5491
|
|
5
|
+
codetool_explore/cursor.py,sha256=-SHdSxUO0gqmrp6lZMtRlfgOo95jJNxl1I_OotPel9U,2113
|
|
6
|
+
codetool_explore/errors.py,sha256=rKaczr3dTmL4ipbuICXyCO5vmyjXGcx85m3YKrX-2UI,663
|
|
7
|
+
codetool_explore/explorer.py,sha256=MVGgQKMZwKQ439OLoXXQ8iyFDAIGkAl40T3etg4f6Nc,16889
|
|
8
|
+
codetool_explore/ignore.py,sha256=rRYT9Irc3X7tZFySDdNSTachXkg0ueNy-Jv4G-Ebw8A,6808
|
|
9
|
+
codetool_explore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
codetool_explore/ranking.py,sha256=s9ekc_rEpy_irC384vekxhYeQXTgTsNf8zSnumYp2Vo,10456
|
|
11
|
+
codetool_explore/roots.py,sha256=DxtP_Ze5Woq2W0k8XiX9vE_7E2Dt1-f6IU4iDJ4bYEc,4557
|
|
12
|
+
codetool_explore/rust_backend.py,sha256=NvYWhsuHUXKY3ukVCktmJBIbz6iGqaX0eFSxPBwYfhI,9867
|
|
13
|
+
codetool_explore/text_output.py,sha256=4mlf28FIVXEJF9DOUp8Jk4KPFQpb2Ry1oH3RwoZ3mhM,14916
|
|
14
|
+
codetool_explore/python_backend/__init__.py,sha256=BpfQ_5ugzAp2PmiGPYtaAMJYWyjeaCeqa6CmuR45QBU,4559
|
|
15
|
+
codetool_explore/python_backend/case.py,sha256=rOUsFhlL6g8La0D4ek1x4j487mloSUeEiUt2qhrGAd4,819
|
|
16
|
+
codetool_explore/python_backend/config.py,sha256=cJuuQUU8WQYlp_bY686IrbHxoBGZYFb8zPulHYwBs8o,1175
|
|
17
|
+
codetool_explore/python_backend/constants.py,sha256=FK_-TOlNR_mrXDTqYhlNnwg4goGYMKXPpSpIc-wcSlc,1303
|
|
18
|
+
codetool_explore/python_backend/file_search.py,sha256=VMF3XXpIoq65RcUnqmN7Mdk_X1yd4VXMTXTSZ-KQR40,1704
|
|
19
|
+
codetool_explore/python_backend/ignore_rules.py,sha256=GEQN4HLr9uCm09cfP45Wm1pa35Qnx4C1XCFTO7ztoYI,1177
|
|
20
|
+
codetool_explore/python_backend/literal.py,sha256=E5FHlIUfYofx7HzuVKGuZD0GBXHgR5NuSdImOZVf_V0,2341
|
|
21
|
+
codetool_explore/python_backend/matcher.py,sha256=WO68HbrNIgRoSqWlQ1jK4YeQTVCdP9e135WMwc-gM_k,2258
|
|
22
|
+
codetool_explore/python_backend/models.py,sha256=b7cYRcRzZxMIS-0ql8pFqThKMOG0pYFrOZF7-_T3snU,1177
|
|
23
|
+
codetool_explore/python_backend/output.py,sha256=A14Kb4IyWUnTpxtuWbaUAuYNAhINDKLTlPgWGeZcygo,2096
|
|
24
|
+
codetool_explore/python_backend/regex_search.py,sha256=Wq6TFdZxtLOjdlHbsa7xLBc11IvIuVFc5csxlfd8B_0,1790
|
|
25
|
+
codetool_explore/python_backend/search.py,sha256=98fCRIy13br1QSA76Cny1jokl1iAKoA84NO5oxrAItY,10968
|
|
26
|
+
codetool_explore/python_backend/text.py,sha256=piBrblyc38EcbBb-gebjwIVP-V-63akkHfSYb4gfyFk,1094
|
|
27
|
+
codetool_explore/python_backend/walker.py,sha256=4fOe914cRAfMtd0eyg2iDjbAWG275prdr6UbacnnnjQ,4497
|
|
28
|
+
codetool_explore/_bin/codetool-explore-rust-windows-x86_64.exe,sha256=rEi8y_nlXmzhnltTjk_9tWkV7YH--y_RmqOUrJ4DmLU,2366464
|
|
29
|
+
codetool_explore-0.5.0.dist-info/METADATA,sha256=7H_J77NsSrKy0ryLBsFe0w1fhcfzLELjds3ibXPehk4,10555
|
|
30
|
+
codetool_explore-0.5.0.dist-info/WHEEL,sha256=OKr2XcpSNWrtUe-CU6RMYrBnsfqGnZ3jZM4vKnozTRA,94
|
|
31
|
+
codetool_explore-0.5.0.dist-info/entry_points.txt,sha256=WoDNNkptvfYXekUyd5CDzOWrTwY7ssk6AGKwdoLmClU,62
|
|
32
|
+
codetool_explore-0.5.0.dist-info/licenses/LICENSE,sha256=Vv1XewZgbAhS6ciB2kfJ8y1-Ouwb23Jtj0KCmJl37VA,1080
|
|
33
|
+
codetool_explore-0.5.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 drod
|
|
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.
|