docker-orb 1.0.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,329 @@
1
+ /* ── Overall layout ──────────────────────────────────────────────────────── */
2
+
3
+ Screen {
4
+ background: $surface;
5
+ }
6
+
7
+ #wizard-title {
8
+ text-align: center;
9
+ text-style: bold;
10
+ color: $accent;
11
+ background: $panel;
12
+ padding: 1 0;
13
+ border-bottom: solid $accent;
14
+ }
15
+
16
+ #wizard-footer {
17
+ text-align: center;
18
+ color: $text-muted;
19
+ background: $panel;
20
+ padding: 0 1;
21
+ border-top: solid $panel-lighten-2;
22
+ }
23
+
24
+ TabbedContent {
25
+ height: 1fr;
26
+ }
27
+
28
+ TabPane {
29
+ padding: 1 2;
30
+ height: 1fr;
31
+ layout: vertical;
32
+ }
33
+
34
+ #targets-scroll, #strings-scroll, #options-scroll {
35
+ height: 1fr;
36
+ }
37
+
38
+ /* ── Section headers ─────────────────────────────────────────────────────── */
39
+
40
+ .section-title {
41
+ text-style: bold;
42
+ color: $accent;
43
+ padding: 1 0 1 2;
44
+ border-left: outer $accent;
45
+ background: $panel;
46
+ margin-top: 1;
47
+ margin-bottom: 1;
48
+ }
49
+
50
+ /* ── Labels and hints ────────────────────────────────────────────────────── */
51
+
52
+ .field-label {
53
+ color: $text-muted;
54
+ margin-bottom: 0;
55
+ padding-left: 1;
56
+ }
57
+
58
+ .field-hint {
59
+ color: $text-muted;
60
+ padding-left: 1;
61
+ margin-bottom: 1;
62
+ }
63
+
64
+ .field-warn {
65
+ color: $warning;
66
+ padding-left: 1;
67
+ margin: 1 0;
68
+ }
69
+
70
+ /* ── Rules ───────────────────────────────────────────────────────────────── */
71
+
72
+ Rule {
73
+ margin: 1 0;
74
+ color: $panel-lighten-2;
75
+ }
76
+
77
+ /* ── Rows ────────────────────────────────────────────────────────────────── */
78
+
79
+ .row {
80
+ height: auto;
81
+ margin-bottom: 1;
82
+ }
83
+
84
+ .row Button {
85
+ margin-right: 1;
86
+ }
87
+
88
+ /* ── Inputs — bright border to stand out ─────────────────────────────────── */
89
+
90
+ Input {
91
+ background: $panel;
92
+ border: tall $primary-lighten-2;
93
+ margin-bottom: 1;
94
+ }
95
+
96
+ Input:focus {
97
+ border: tall $accent;
98
+ }
99
+
100
+ /* ── Select ──────────────────────────────────────────────────────────────── */
101
+
102
+ Select {
103
+ margin-bottom: 1;
104
+ width: auto;
105
+ max-width: 60;
106
+ }
107
+
108
+ /* Namespace select sits in a row with a button — let it grow to fill */
109
+ #ns-select {
110
+ width: 1fr;
111
+ max-width: 60;
112
+ margin-bottom: 0;
113
+ }
114
+
115
+ Select > SelectCurrent {
116
+ background: $panel;
117
+ border: tall $primary-lighten-2;
118
+ }
119
+
120
+ Select:focus > SelectCurrent {
121
+ border: tall $accent;
122
+ }
123
+
124
+ /* ── Namespace ───────────────────────────────────────────────────────────── */
125
+
126
+ #ns-select {
127
+ margin-bottom: 0;
128
+ }
129
+
130
+ #ns-input {
131
+ width: 1fr;
132
+ max-width: 60;
133
+ margin-bottom: 0;
134
+ }
135
+
136
+
137
+ /* ── Saved config select ─────────────────────────────────────────────────── */
138
+
139
+ #cfg-select {
140
+ width: 1fr;
141
+ }
142
+
143
+ /* ── Deployment list ─────────────────────────────────────────────────────── */
144
+
145
+ .btn-small {
146
+ min-width: 12;
147
+ }
148
+
149
+ #dep-scroll {
150
+ height: 7;
151
+ width: 64;
152
+ background: $panel;
153
+ border: tall $primary-lighten-2;
154
+ margin-bottom: 0;
155
+ scrollbar-size-vertical: 2;
156
+ scrollbar-color: $accent;
157
+ scrollbar-color-hover: $accent-lighten-1;
158
+ scrollbar-background: $panel-darken-1;
159
+ }
160
+
161
+ #dep-checks {
162
+ height: auto;
163
+ }
164
+
165
+ /* ── Checkboxes ──────────────────────────────────────────────────────────── */
166
+
167
+ Checkbox {
168
+ height: auto;
169
+ margin: 0;
170
+ padding: 0 1;
171
+ background: transparent;
172
+ border: none;
173
+ }
174
+
175
+ Checkbox > .toggle--button {
176
+ color: $panel;
177
+ background: $panel-darken-1;
178
+ }
179
+
180
+ Checkbox.-on > .toggle--button {
181
+ color: $text;
182
+ background: $success;
183
+ text-style: bold;
184
+ }
185
+
186
+ Checkbox:focus {
187
+ background: $panel-lighten-1;
188
+ }
189
+
190
+ Checkbox:hover {
191
+ background: $panel-lighten-1;
192
+ }
193
+
194
+ /* ── RadioSet ────────────────────────────────────────────────────────────── */
195
+
196
+ RadioSet {
197
+ background: $panel;
198
+ border: tall $primary-lighten-2;
199
+ padding: 0 1;
200
+ height: auto;
201
+ width: 48;
202
+ margin-bottom: 1;
203
+ }
204
+
205
+ RadioButton {
206
+ background: transparent;
207
+ }
208
+
209
+ /* ── Dump options ────────────────────────────────────────────────────────── */
210
+
211
+ #dump-opts {
212
+ padding: 0 0 0 2;
213
+ margin-top: 0;
214
+ margin-bottom: 1;
215
+ height: auto;
216
+ }
217
+
218
+ #dump-tail, #dump-since {
219
+ width: 24;
220
+ }
221
+
222
+ /* ── Stream options ───────────────────────────────────────────────────────── */
223
+
224
+ #stream-opts {
225
+ padding: 0 0 0 2;
226
+ margin-top: 0;
227
+ margin-bottom: 1;
228
+ height: auto;
229
+ }
230
+
231
+ #stream-since {
232
+ width: 40;
233
+ }
234
+
235
+ /* ── Patterns edit row ───────────────────────────────────────────────────── */
236
+
237
+ #patterns-edit-row {
238
+ align: left middle;
239
+ height: auto;
240
+ margin-bottom: 0;
241
+ }
242
+
243
+ #patterns-file-hint {
244
+ width: 1fr;
245
+ margin-bottom: 0;
246
+ padding-left: 1;
247
+ color: $text-muted;
248
+ }
249
+
250
+ /* ── String check lists ──────────────────────────────────────────────────── */
251
+
252
+ #filter-scroll, #hl-scroll, #mon-scroll {
253
+ height: 7;
254
+ width: 64;
255
+ background: $panel;
256
+ border: tall $primary-lighten-2;
257
+ margin-bottom: 0;
258
+ scrollbar-size-vertical: 2;
259
+ scrollbar-color: $accent;
260
+ scrollbar-color-hover: $accent-lighten-1;
261
+ scrollbar-background: $panel-darken-1;
262
+ }
263
+
264
+ #filter-checks, #hl-checks, #mon-checks {
265
+ height: auto;
266
+ }
267
+
268
+ /* Dim label above saved list to distinguish from the "Add new" input below */
269
+ .saved-label {
270
+ color: $text-muted;
271
+ padding-left: 1;
272
+ }
273
+
274
+ /* Accent the "Add new" label so it reads as a distinct action area */
275
+ .new-label {
276
+ color: $accent;
277
+ padding-left: 1;
278
+ margin-top: 1;
279
+ }
280
+
281
+ .option-row {
282
+ height: auto;
283
+ margin-top: 0;
284
+ margin-bottom: 1;
285
+ }
286
+
287
+ .option-row Checkbox {
288
+ width: 1fr;
289
+ }
290
+
291
+ /* ── Health options ──────────────────────────────────────────────────────── */
292
+
293
+ #health-opts {
294
+ padding: 0 0 0 2;
295
+ height: auto;
296
+ margin-top: 0;
297
+ margin-left: 0;
298
+ }
299
+
300
+ #health-interval {
301
+ width: 16;
302
+ }
303
+
304
+ #health-section, #monitor-section {
305
+ height: auto;
306
+ }
307
+
308
+ /* ── Options tab ─────────────────────────────────────────────────────────── */
309
+
310
+ #save-name {
311
+ margin-bottom: 0;
312
+ }
313
+
314
+ .nav-row {
315
+ margin-top: 1;
316
+ height: auto;
317
+ }
318
+
319
+ .btn-cancel {
320
+ width: 1fr;
321
+ }
322
+
323
+ .btn-back {
324
+ width: 1fr;
325
+ }
326
+
327
+ .btn-next {
328
+ width: 3fr;
329
+ }
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: docker-orb
3
+ Version: 1.0.0
4
+ Summary: Interactive terminal UI for streaming and filtering Docker Compose container logs across multiple services
5
+ Project-URL: Homepage, https://github.com/adlidev/docker-orb
6
+ Project-URL: Repository, https://github.com/adlidev/docker-orb
7
+ Project-URL: Documentation, https://github.com/adlidev/docker-orb/blob/main/docs/USER_GUIDE.md
8
+ Project-URL: Issues, https://github.com/adlidev/docker-orb/issues
9
+ Author-email: Adli Waziri <boulderadli@gmail.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Adli Waziri
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: compose,devops,docker,docker-compose,logs,observability,sre,textual,tui
33
+ Classifier: Development Status :: 5 - Production/Stable
34
+ Classifier: Environment :: Console
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: Intended Audience :: System Administrators
37
+ Classifier: License :: OSI Approved :: MIT License
38
+ Classifier: Operating System :: MacOS
39
+ Classifier: Operating System :: POSIX :: Linux
40
+ Classifier: Programming Language :: Python :: 3
41
+ Classifier: Programming Language :: Python :: 3.10
42
+ Classifier: Programming Language :: Python :: 3.11
43
+ Classifier: Programming Language :: Python :: 3.12
44
+ Classifier: Topic :: System :: Logging
45
+ Classifier: Topic :: System :: Monitoring
46
+ Classifier: Topic :: System :: Systems Administration
47
+ Classifier: Topic :: Utilities
48
+ Requires-Python: >=3.10
49
+ Requires-Dist: click>=8.1
50
+ Requires-Dist: pyyaml>=6.0
51
+ Requires-Dist: textual>=0.61.0
52
+ Description-Content-Type: text/markdown
53
+
54
+ # docker-orb
55
+
56
+ Watching logs across a multi-container Compose stack usually means juggling
57
+ multiple `docker compose logs` windows and grepping after the fact, or
58
+ writing complex regex just to add a couple of filters. docker-orb streams
59
+ logs from every container in your selected services into one merged,
60
+ colorized view — and lets you filter, highlight, and search *while it's
61
+ streaming*.
62
+
63
+ Built with [Textual](https://textual.textualize.io/), docker-orb is a full
64
+ terminal UI: an interactive setup wizard walks you through picking a
65
+ project, services, and filter patterns — no flags required — or skip
66
+ straight to the viewer with CLI arguments if you prefer. Save named session
67
+ configs to reload your exact setup later; passively collect rare events in
68
+ a monitor panel without losing your scroll position; get alerted when
69
+ containers restart or go unhealthy; and add or remove services from the
70
+ live stream without restarting.
71
+
72
+ ![docker-orb viewer](docs/screenshot-viewer.png)
73
+
74
+ ## Features
75
+
76
+ - **Multi-container live tail** — stream logs from any number of
77
+ containers/services at once, each with a distinct color.
78
+ - **Filters** — hide lines matching a pattern, live-editable mid-session.
79
+ - **Highlights** — emphasize lines matching a pattern without hiding anything else.
80
+ - **Monitors** — passively collect matching lines into a separate panel so you
81
+ can watch for rare events (e.g. `job failed`) without losing your scroll
82
+ position in the main stream.
83
+ - **Live search** — search the whole buffered session, jump to a result, and
84
+ keep streaming.
85
+ - **Container health panel** — flags containers that aren't `Running` or have
86
+ crossed a restart threshold; restart a container or recreate a service from
87
+ inside the app.
88
+ - **Dump mode** — fetch existing logs once (with `--tail`/`--since`) and exit,
89
+ for scripting or one-off inspection, instead of a live stream.
90
+ - **Saved configs** — save a named combination of project, services, and
91
+ patterns, and reload it later by name.
92
+ - **JSON log formatting** — auto-detects structured JSON log lines and shows
93
+ `time LEVEL message key=value ...` instead of a raw JSON blob, with a
94
+ toggle back to raw and a detail view (Enter on a clicked line) for the full
95
+ pretty-printed object.
96
+ - **Collapse repeated lines** — journalctl-style: consecutive identical lines
97
+ from the same container fold into a single "last line repeated N times"
98
+ marker, toggleable, so a crash-looping container doesn't flood the stream.
99
+
100
+ ## Installation
101
+
102
+ Requires Python 3.10+, and `docker` with the Compose v2 plugin on your
103
+ `PATH` (i.e. `docker compose` works). docker-orb shells out to your existing
104
+ `docker` CLI and inherits its context/auth — no separate credentials or
105
+ Docker SDK involved.
106
+
107
+ ```bash
108
+ # Recommended — pipx keeps CLI tools in isolated environments. Not on PyPI
109
+ # yet, so install straight from GitHub:
110
+ pipx install git+https://github.com/adlidev/docker-orb
111
+
112
+ # Or with plain pip, inside your own venv:
113
+ git clone https://github.com/adlidev/docker-orb.git
114
+ cd docker-orb
115
+ pip install -e .
116
+ ```
117
+
118
+ This installs two commands: `docker-orb` and `docker-orb-inject` (a small
119
+ test utility for injecting fake log lines into a running container — see
120
+ the [User Guide](docs/USER_GUIDE.md#docker-orb-inject)).
121
+
122
+ **For development / contributing:**
123
+
124
+ ```bash
125
+ git clone https://github.com/adlidev/docker-orb.git
126
+ cd docker-orb
127
+ pip install -e . --group dev
128
+ ```
129
+
130
+ ## Quick start
131
+
132
+ ```bash
133
+ # No arguments — launches the interactive setup wizard
134
+ docker-orb
135
+
136
+ # Skip the wizard: watch two services live
137
+ docker-orb -n myapp -p api -p worker
138
+
139
+ # Watch every service in a project
140
+ docker-orb -n myapp --all-containers
141
+
142
+ # One-shot dump instead of a live stream
143
+ docker-orb -n myapp --dump --tail 500
144
+
145
+ # Pre-filter/highlight from the command line
146
+ docker-orb -n myapp -p worker -f DEBUG -H ERROR --health
147
+ ```
148
+
149
+ `-n/--project` takes the Compose project name (what `docker compose -p`
150
+ expects — usually the directory name your `docker-compose.yml` lives in,
151
+ unless overridden by a top-level `name:` key or `COMPOSE_PROJECT_NAME`). If
152
+ omitted, docker-orb resolves it the same way `docker compose` does for the
153
+ current directory.
154
+
155
+ Once you're in the viewer, press `F` / `H` / `M` to edit filters, highlights,
156
+ or monitors live, `/` to search, `P` to add/remove streamed services, and
157
+ `Space` to pause. See the [User Guide](docs/USER_GUIDE.md) for the full
158
+ keybinding reference and pattern syntax.
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ pip install -e . --group dev
164
+ pytest # run the test suite
165
+ textual run --dev src/docker_orb/__main__.py # live-reload dev mode
166
+ ```
167
+
168
+ A local Compose stack with 8 fake log-generating services is included for
169
+ manual testing:
170
+
171
+ ```bash
172
+ cd infra
173
+ make up
174
+ docker-orb -n logviewer-demo --all-containers --health
175
+ ```
176
+
177
+ See [`infra/README.md`](infra/README.md) for the service list and good
178
+ filter/monitor test patterns, and [`CLAUDE.md`](CLAUDE.md) for architecture
179
+ notes.
180
+
181
+ ## Relationship to kube-orb
182
+
183
+ docker-orb is a sibling project to [kube-orb](https://github.com/adlidev/kube-orb),
184
+ Adli's Kubernetes-flavored log viewer. They share a UI architecture (Textual
185
+ viewer, wizard, filters/highlights/monitors, JSON formatting) but each talks
186
+ to its own backend directly — docker-orb shells out to `docker`/`docker
187
+ compose`, kube-orb shells out to `kubectl` — and they're maintained as
188
+ independent tools rather than one project trying to do both.
189
+
190
+ ## License
191
+
192
+ [MIT License](LICENSE)
@@ -0,0 +1,28 @@
1
+ docker_orb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ docker_orb/__main__.py,sha256=yq_aInmV6HCZN0Hl1TpmfBSErrRJNtSisb8tTfqi_8E,140
3
+ docker_orb/_scrollbar.py,sha256=U_1BiB70LJMpWXQnlkj25AAzNKEe5kqtzWoZhmYK34s,5344
4
+ docker_orb/cli.py,sha256=mEYpwLVNoKAW-LtYnhm3eu57nNA_reLizZJ1t0AHJJ8,7390
5
+ docker_orb/colors.py,sha256=Ufe21cwg3nToiILeTshU5g1FWjfYkIT6cQmzvGa_3n4,2109
6
+ docker_orb/compose.py,sha256=8ClUrO632V4N2_uIfZRSgHhUKFTo671_NTxqEeDD2eg,14659
7
+ docker_orb/config.py,sha256=V6DZOapozTvC4n0PQXcko9uennzeEwq1ehV0mrtlnNg,8766
8
+ docker_orb/inject.py,sha256=or0M8_hy_pDChCfK_zlT10kZcBys6pmkvTjzO-1O2X4,5714
9
+ docker_orb/jsonlog.py,sha256=134VJrKseHksLKD2_yNxisYFcrPuRdE9lTydaAsc_2Q,4627
10
+ docker_orb/models.py,sha256=qxrd8Y7nMKzWT60nkjB6liiplpZT9xi8wRZT7rAJW3M,4807
11
+ docker_orb/viewer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ docker_orb/viewer/app.py,sha256=8rXamUBqDEFOqrFsAMxNDWa9pjIIOWrhNULBAIgArX0,37356
13
+ docker_orb/viewer/viewer.tcss,sha256=D87cfT-3ilimIysNurvlmTPPGb02DvxPelmC4OEkcf0,4554
14
+ docker_orb/viewer/widgets.py,sha256=4CXxlm7mO5dq7qUfx3VyAKdxJ-xXl3ydmjKwzgH7ruM,23322
15
+ docker_orb/viewer/panels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ docker_orb/viewer/panels/health.py,sha256=oB_jPVP5KskTfOUQB6TX_x2SdswagZkk0rFZJBu7Zhs,8897
17
+ docker_orb/viewer/panels/main_stream.py,sha256=Lz2Vno9Ac9i7sRH07mmITVD_kEibS_cYFAvg9m1tir8,15740
18
+ docker_orb/viewer/panels/monitor.py,sha256=2S5gWReXC5lK1djRDGcrIgYpoTD8eVY-PRCqMV2xS38,7990
19
+ docker_orb/viewer/panels/search.py,sha256=0pRqLmAoTtgawAfQNBkWqNEqsxbVScA-YeWRMY2VMK8,6192
20
+ docker_orb/wizard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ docker_orb/wizard/app.py,sha256=XPqo74kjz7VWevCKW3jKfFVWjXQz48vIBnUsKcCkrD0,986
22
+ docker_orb/wizard/screens.py,sha256=n4ss7YvLGRHM7Ft779faQy7AXQy_Ofwfop4GlSMp37s,32838
23
+ docker_orb/wizard/wizard.tcss,sha256=i7tRSWti7Vqjdmp6azmC2AMkdQRsjoyCBRRPfxeGVGs,7908
24
+ docker_orb-1.0.0.dist-info/METADATA,sha256=UfdiGXQjfQKE37hoyV8kvGd66dVgOhpdp-XT8cJn83s,8144
25
+ docker_orb-1.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
26
+ docker_orb-1.0.0.dist-info/entry_points.txt,sha256=KxDwYM0UTmOb9mhhvQbhc9cOMofb0vJkZ3yPRe9hNmA,94
27
+ docker_orb-1.0.0.dist-info/licenses/LICENSE,sha256=zVfZOlwrrTMcNn4XNJED1CoLkon34cOGUn6R-mtqf04,1068
28
+ docker_orb-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ docker-orb = docker_orb.cli:main
3
+ docker-orb-inject = docker_orb.inject:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adli Waziri
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.