nightshift-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,430 @@
1
+ Metadata-Version: 2.4
2
+ Name: nightshift-cli
3
+ Version: 0.1.0
4
+ Summary: Your AI works the night shift — read-only reviews of your projects while you're busy, one digest every morning.
5
+ Project-URL: Homepage, https://github.com/kishormorol/nightshift
6
+ Project-URL: Source, https://github.com/kishormorol/nightshift
7
+ Author: Md. Kishor Morol
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai,automation,claude-code,cli,code-review
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Quality Assurance
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: click>=8.1
21
+ Requires-Dist: pyyaml>=6.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
24
+ Requires-Dist: pytest>=7.4; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ <div align="center">
28
+
29
+ # nightshift
30
+
31
+ **Your AI works the night shift.**
32
+
33
+ Put your idle Claude Code subscription to work — read-only reviews of your
34
+ projects while you're busy, one digest every morning.
35
+
36
+ ![A real code_review of this repo: two HIGH findings, then the morning digest](docs/demo.svg)
37
+
38
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
39
+ [![python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)
40
+
41
+ </div>
42
+
43
+ ---
44
+
45
+ ## What this is
46
+
47
+ You already pay for Claude Code. It sits idle most of the day, and definitely
48
+ all night. nightshift spends that idle time reviewing the projects you point it
49
+ at, and leaves the results in one Markdown file you read with your coffee.
50
+
51
+ It never edits your code. It has no daemon and no server — cron calls it, it
52
+ decides whether to run, and it goes back to sleep. Everything it knows lives in
53
+ two directories you can delete at any time.
54
+
55
+ That's the whole tool. The rest of this page is how to use it.
56
+
57
+ ## Before you start
58
+
59
+ - **Python 3.10+**
60
+ - **At least one AI CLI**, installed and already logged in — either
61
+ [Claude Code](https://claude.com/claude-code) or
62
+ [Codex](https://developers.openai.com/codex). Run `claude --version` or
63
+ `codex --version`; if that works, nightshift will find it. Have both and it
64
+ will use both, each with its own budget.
65
+ - **cron** — standard on macOS and Linux. Windows works via WSL.
66
+
67
+ You do not need an API key. nightshift drives the same CLIs you use by hand, on
68
+ the subscriptions you already have.
69
+
70
+ ## Install it
71
+
72
+ ```bash
73
+ pipx install nightshift-cli
74
+ ```
75
+
76
+ (`pip install nightshift-cli` works too; pipx just keeps it out of your other
77
+ environments. The command is `nightshift` either way.)
78
+
79
+ ## Set it up
80
+
81
+ Run `nightshift init` once. It finds your AI CLIs, asks which projects to
82
+ review and when, writes a config file, and offers to install the cron lines
83
+ that drive everything:
84
+
85
+ ![nightshift init walks you through detection, projects, schedule, and cron](docs/img/init.svg)
86
+
87
+ The project path is the only thing it needs from you. Everything else has a
88
+ working default you can accept with Enter:
89
+
90
+ | It asks | It means | Default |
91
+ | --- | --- | --- |
92
+ | **project path** | A repo to review. Enter as many as you like; blank line to finish. | — |
93
+ | **name** | What to call it in the digest. | the directory's name |
94
+ | **tasks** | Which reviews to run on it. | `code_review, security_audit, deps_audit` |
95
+ | **windows** | Hours it's allowed to run, local time. | `00:00-06:00` |
96
+ | **idle minutes** | How long you must be away from Claude Code first. | `60` |
97
+ | **digest dir** | Where the morning digest lands. | `~/nightshift-reports` |
98
+
99
+ Everything it writes goes to `~/.nightshift/config.yaml`. Edit it by hand
100
+ whenever you like — it's plain YAML, and `nightshift status` validates it.
101
+
102
+ ## See it work
103
+
104
+ Don't wait until tonight. Force a run right now:
105
+
106
+ ```bash
107
+ nightshift run --now
108
+ ```
109
+
110
+ `--now` skips the window and idle checks, so you get a review immediately. At a
111
+ terminal, nightshift streams it live — you see the same reads and reasoning you
112
+ would if you'd run `claude` yourself:
113
+
114
+ ![A live nightshift run: reads, reasoning, then seven findings ranked by severity](docs/img/watch.svg)
115
+
116
+ That is a real run of nightshift against its own repository, and those are real
117
+ bugs. Two of them became commits the same evening: a run that could hang forever
118
+ holding the scheduler's lock (`claude_code.py:366`), and a lock that could be
119
+ released by the wrong owner (`lock.py:121`).
120
+
121
+ Findings are ranked 🔴 HIGH, 🟠 MED, 🟡 LOW, and each one cites a `file:line`
122
+ so you can jump straight to it.
123
+
124
+ ## Then forget about it
125
+
126
+ If you let `init` install the cron lines, you're already done. Cron calls
127
+ `nightshift run` every hour; the command decides for itself whether to act and
128
+ exits quietly when the answer is no:
129
+
130
+ ```
131
+ 0 * * * * ~/.local/bin/nightshift run >> /tmp/nightshift-cron.log 2>&1
132
+ 30 7 * * * ~/.local/bin/nightshift digest >> /tmp/nightshift-cron.log 2>&1
133
+ ```
134
+
135
+ The hourly line is the gated one; the 07:30 line renders yesterday's findings.
136
+ `init` writes the absolute path to the binary because cron runs with a minimal
137
+ `PATH` that almost certainly doesn't include yours, and redirects both streams
138
+ to a log because there is nowhere else for cron's output to go.
139
+
140
+ A run happens only when **all four gates** open:
141
+
142
+ 1. **Window** — the clock is inside one of your `schedule.windows`.
143
+ 2. **Idle** — you haven't touched Claude Code for `idle_minutes`. nightshift
144
+ watches `~/.claude/projects` and stays out of your way while you're working.
145
+ 3. **Budget** — you have runs left today and this week.
146
+ 4. **Lock** — no other run is already in flight.
147
+
148
+ Any gate saying no is normal, not an error. It prints one line and exits 0:
149
+
150
+ ```
151
+ $ nightshift run
152
+ nothing to do — outside configured windows (00:00-06:00)
153
+ ```
154
+
155
+ Each run pops one `(project, task)` pair from a persistent round-robin queue,
156
+ so every project gets its turn and a noisy one can't starve the rest.
157
+
158
+ To check on it any time, ask:
159
+
160
+ ![nightshift status: budget bars, next window, what's up next, recent runs](docs/img/status.svg)
161
+
162
+ And to watch a run that cron started — including one already in progress —
163
+ `nightshift watch` follows along live and replays the last finished run first.
164
+
165
+ ## Read the digest
166
+
167
+ Every run appends to `~/nightshift-reports/YYYY-MM-DD/`. Once a day
168
+ `nightshift digest` renders those into one file, `DIGEST-YYYY-MM-DD.md`:
169
+
170
+ ```markdown
171
+ # Nightshift · morning digest
172
+
173
+ Wed Jul 15, 2026 · generated 17:57 local · 1 project · 1 run
174
+
175
+ ## Budget remaining
176
+
177
+ - `claude_code` ▓░░░░░ 1/6 today · 1/30 week
178
+
179
+ ## Highlights
180
+
181
+ - 🔴 Replace the buffered `subprocess.run(..., timeout=...)` with the same `Popen` +
182
+ `os.killpg` treatment the streaming path uses … — _nightshift · code_review_ ·
183
+ `nightshift/adapters/claude_code.py:267`
184
+ - 🟠 Have `release()` re-read the lockfile and unlink only when the recorded pid is
185
+ still our own … — _nightshift · code_review_ · `nightshift/lock.py:121`
186
+
187
+ ## Run log
188
+
189
+ | project | task | provider | status | dur | time |
190
+ | --- | --- | --- | --- | --- | --- |
191
+ | nightshift | code_review | claude_code | ok | 2m18s | 15:23 |
192
+ ```
193
+
194
+ Highest severity first, grouped by project, read in twenty seconds.
195
+ **[Here is that digest in full](docs/sample-digest.md)** — a real one, not a
196
+ mock-up.
197
+
198
+ Skipped and failed runs stay in the log. A run that didn't happen is
199
+ information too, and silently dropping it is how you stop trusting the tool.
200
+
201
+ Want it early, or for a specific day?
202
+
203
+ ```bash
204
+ nightshift digest # today, written to the digest dir
205
+ nightshift digest --date 2026-07-14 # a past day
206
+ nightshift digest --stdout # print it instead of writing it
207
+ ```
208
+
209
+ ## 0 files touched
210
+
211
+ nightshift's entire value depends on being safe to leave unattended, so
212
+ read-only isn't a promise in the docs — it's enforced at the layer that
213
+ actually executes tools.
214
+
215
+ The Claude Code adapter invokes the CLI with its own permission flags:
216
+
217
+ ```
218
+ claude --print "<prompt>"
219
+ --output-format json
220
+ --allowed-tools Read Grep Glob NotebookRead
221
+ --disallowed-tools Bash Edit MultiEdit Write NotebookEdit WebFetch WebSearch Task
222
+ ```
223
+
224
+ Three things are true because of that:
225
+
226
+ - **The allowlist is the whole tool budget.** Claude Code cannot call a tool
227
+ that isn't on it. There is no Edit, no Write, no shell.
228
+ - **The denylist is belt-and-braces.** It exists so that a future CLI release
229
+ adding a new write-capable tool to the defaults can't silently widen what
230
+ nightshift can do.
231
+ - **No fallback.** If the flags are rejected, the run fails and is logged as
232
+ failed. nightshift never retries an unrestricted invocation.
233
+
234
+ The prompts reinforce it, but prompts are not a security boundary and aren't
235
+ treated as one. The flags are.
236
+
237
+ nightshift also never touches your git state: no commits, no branches, no
238
+ pushes. It reads, and it writes exactly one place — the digest directory.
239
+
240
+ ## Don't let it burn your quota
241
+
242
+ nightshift runs on the subscription you already pay for, which means the
243
+ fastest way for it to become a problem is to burn through your quota. So it
244
+ counts.
245
+
246
+ ```yaml
247
+ providers:
248
+ claude_code:
249
+ enabled: true
250
+ budget:
251
+ max_runs_per_day: 6
252
+ max_runs_per_week: 30
253
+ ```
254
+
255
+ - **Every attempt counts** — including failures and timeouts. They spent your
256
+ quota, so they cost budget. Counting only successes would let a broken
257
+ project drain the account in a loop.
258
+ - **Both caps bind.** Under the daily cap but at the weekly one? It stops.
259
+ - **`--now` skips the window and idle checks, never the budget check.**
260
+ - **At the cap it stops and says so**, once, as a `skipped` row in the digest.
261
+
262
+ Start low. Six runs a day is already a lot of review.
263
+
264
+ ## Configuration
265
+
266
+ Lives at `~/.nightshift/config.yaml`. Here it is in full — this is every knob
267
+ there is:
268
+
269
+ ```yaml
270
+ providers:
271
+ claude_code:
272
+ enabled: true
273
+ budget: { max_runs_per_day: 6, max_runs_per_week: 30 }
274
+ codex:
275
+ enabled: true
276
+ budget: { max_runs_per_day: 6, max_runs_per_week: 30 }
277
+ # Optional. Only needed when the CLI isn't on PATH under its own name —
278
+ # e.g. the Codex bundled inside ChatGPT.app:
279
+ binary: /Applications/ChatGPT.app/Contents/Resources/codex
280
+
281
+ projects:
282
+ - name: gradagent
283
+ path: ~/projects/gradagent
284
+ tasks: [code_review, deps_audit, docs_drift]
285
+ - name: nightshift
286
+ path: ~/projects/nightshift
287
+ tasks: [code_review]
288
+ # Optional. Pin this project to one provider. Without it, whichever enabled
289
+ # provider is idle and under budget takes the project.
290
+ provider: codex
291
+
292
+ schedule:
293
+ windows: ["09:00-18:00", "00:00-06:00"] # local time; may cross midnight
294
+ idle_minutes: 60
295
+
296
+ digest:
297
+ dir: ~/nightshift-reports
298
+ run:
299
+ timeout_s: 600
300
+ ```
301
+
302
+ Run `nightshift status` after editing — it validates the file and tells you
303
+ exactly what's wrong if anything is.
304
+
305
+ Set `NIGHTSHIFT_HOME` to move the whole state directory somewhere else.
306
+
307
+ ## Tasks
308
+
309
+ A task is just a prompt template. Five ship with nightshift:
310
+
311
+ | task | what it looks for |
312
+ | --- | --- |
313
+ | `code_review` | Bugs, races, and correctness problems |
314
+ | `security_audit` | Injection, authz gaps, unsafe defaults |
315
+ | `deps_audit` | Unpinned, stale, or risky dependencies |
316
+ | `docs_drift` | Docs that no longer match the code |
317
+ | `dead_links` | Links and image paths pointing at things that aren't there |
318
+
319
+ Give each project the tasks that suit it — a Terraform repo probably wants
320
+ `security_audit` and `deps_audit`, not `dead_links`.
321
+
322
+ **Write your own:** drop any `.md` file into `~/.nightshift/prompts/` and its
323
+ filename becomes a valid task name. Use a shipped name to override that
324
+ template.
325
+
326
+ Templates must tell the model to prefix each finding with `HIGH`, `MED`, or
327
+ `LOW` and cite a `file:line`. Parsing is lenient — an unlabelled finding is
328
+ kept and filed as `LOW` rather than dropped.
329
+
330
+ ## Commands
331
+
332
+ | command | what it does |
333
+ | --- | --- |
334
+ | `nightshift init` | Detect CLIs, register projects, write config, offer to install cron |
335
+ | `nightshift run [--now]` | One gated run. `--now` skips window+idle checks |
336
+ | `nightshift watch [-n N]` | Follow runs live, including ones cron started |
337
+ | `nightshift digest [--date]` | Render `DIGEST-YYYY-MM-DD.md` |
338
+ | `nightshift status` | Budget bars, recent runs, next window, provider health |
339
+
340
+ Each takes `--help`.
341
+
342
+ ## Troubleshooting
343
+
344
+ **`nothing to do — outside configured windows (00:00-06:00)`**
345
+ Working as designed — it's not in a window. Use `nightshift run --now` to run
346
+ anyway, or widen `schedule.windows`.
347
+
348
+ **`nothing to do — claude_code used 12m ago (needs 60m idle)`**
349
+ You've been using Claude Code, so nightshift is staying out of your way. Use
350
+ `--now`, or lower `idle_minutes` (`0` disables the check).
351
+
352
+ **`nothing to do — claude_code: daily budget spent (6/6 today)`**
353
+ Out of quota for today. Raise `max_runs_per_day` if you want more.
354
+
355
+ **``No usable AI CLI found. Install Claude Code or Codex and re-run `nightshift init`.``**
356
+ Neither CLI is on your `PATH`. Check `claude --version` / `codex --version` in
357
+ the same shell.
358
+
359
+ **Cron never runs it.** Cron uses a minimal `PATH`, which is why `init` writes
360
+ the absolute path to the binary into your crontab. Check
361
+ `/tmp/nightshift-cron.log` — everything cron runs is logged there. On macOS,
362
+ cron may also need Full Disk Access to read your projects.
363
+
364
+ **A run is stuck.** Runs are killed at `run.timeout_s` (default 600s) and
365
+ recorded as `timeout`. There is nothing to clean up by hand.
366
+
367
+ ## Uninstall
368
+
369
+ nightshift keeps no state anywhere else, so removing it is three lines:
370
+
371
+ ```bash
372
+ crontab -e # delete the block (see below)
373
+ rm -rf ~/.nightshift # config, ledger, queue, event logs
374
+ pipx uninstall nightshift-cli
375
+ ```
376
+
377
+ `init` fences its crontab lines between two markers — delete them and
378
+ everything between:
379
+
380
+ ```
381
+ # nightshift (managed — edit via `nightshift init`)
382
+ ...
383
+ # end nightshift
384
+ ```
385
+
386
+ Your digests in `~/nightshift-reports` are yours — delete them or don't.
387
+
388
+ ## Providers
389
+
390
+ The scheduler, ledger, queue, and digest are all provider-agnostic. Enable
391
+ whichever CLIs you have; each gets its own budget.
392
+
393
+ | provider | status | how read-only is enforced |
394
+ | --- | --- | --- |
395
+ | `claude_code` | working | CLI permission flags — an allowlist of read-class tools, plus a denylist of every mutating one |
396
+ | `codex` | working | Codex's own OS sandbox — Seatbelt on macOS, Landlock + seccomp on Linux |
397
+ | `copilot` | stub | — see below |
398
+
399
+ One hard requirement, and it's the whole reason that last column exists: **the
400
+ read-only guarantee must be enforced by the CLI's own permission system**, not
401
+ by asking the model nicely. An adapter that can't do that won't be merged,
402
+ because "0 files touched" is the whole product.
403
+
404
+ **Copilot: help wanted, but blocked upstream.** It ships as a documented stub
405
+ (`nightshift/adapters/copilot.py`). The obstacle isn't effort — it's that
406
+ Copilot CLI has no enforcement primitive that clears the bar. Its file-level
407
+ denials don't apply across tools, so `shell(cat x)` walks around a denied
408
+ `read(x)`, and [an open issue](https://github.com/github/copilot-cli/issues/2722)
409
+ reports `--deny-tool="read(...)"` blocking *all* reads regardless of pattern.
410
+ Denials one tool honors and another ignores aren't a permission system. If that
411
+ changes upstream, the adapter is an afternoon's work — `codex.py` and
412
+ `claude_code.py` are both reference shapes.
413
+
414
+ ## Development
415
+
416
+ ```bash
417
+ python -m venv .venv && .venv/bin/pip install -e ".[dev]"
418
+ .venv/bin/pytest
419
+ ```
420
+
421
+ The test suite spends zero quota: the scheduler, budget, queue, and digest are
422
+ covered against a `FakeAdapter`, and the Claude Code adapter is tested with a
423
+ mocked `subprocess`. No test ever shells out to a real AI CLI.
424
+
425
+ The images on this page are generated from real captured output — see
426
+ [docs/RECORDING.md](docs/RECORDING.md) if you change what the CLI prints.
427
+
428
+ ## License
429
+
430
+ MIT
@@ -0,0 +1,30 @@
1
+ nightshift/__init__.py,sha256=GyOUhY4XPP8mihZSot0EEBLfQ9B72hlKA2HR51y61z0,182
2
+ nightshift/__main__.py,sha256=eRvrBBksxLt3oS2RPdh9tVibE3MS60o6NnTV23CG86Y,71
3
+ nightshift/budget.py,sha256=lq-vA-WOU4WRvOFLnEvA4nE0fwpoUyCG1quGWhtrg_k,4856
4
+ nightshift/cli.py,sha256=MFby2PA-9mJo5K57PCWWuQTiKGt7pk7xteQqtF5nMXY,20992
5
+ nightshift/config.py,sha256=XvZ7nz-lJ7Xo7ykbfeekOxQQ5sZg67mIvGEeSaozXIY,16340
6
+ nightshift/cron.py,sha256=qtCMDbJf7QwakFpK7yXwmrUCJwvbEG4ALaxuP6KiTwc,2992
7
+ nightshift/events.py,sha256=CeS7EWjrYDuwJr8WsfKgvG5UGe55qVsLKErCEC8UnN4,9198
8
+ nightshift/lock.py,sha256=P9P8tj8eR1oFX3sDro4nhxzsSEcHQka6BceKcp--iVU,6975
9
+ nightshift/prompts.py,sha256=GK3m5cvQqC4pOPsZNPVCxchIMlgwhm9KsTUASDeV6Dg,1916
10
+ nightshift/queue.py,sha256=2tNUQ12v1a4jWYVjcKb12eS6KASP4UsCsThYujmENeU,3106
11
+ nightshift/report.py,sha256=r2B5FMT7UFMmfgeQUJtq-6sYlSck3gwGT9AckMfbfs4,13127
12
+ nightshift/scheduler.py,sha256=5euLIh3Arn9sk7cIltpyY-OkdUhzmp32fKPBUwUpd_A,14920
13
+ nightshift/sessions.py,sha256=9lWbBBWYDIK5g9TLHh4ojXxIOMNAu2bdHn_Z_DAcFhY,2479
14
+ nightshift/store.py,sha256=I4gBnMO8uHNC4MeWCZKH3BCQO8KJlq1m_LENDl9JBpY,1453
15
+ nightshift/adapters/__init__.py,sha256=9QOJ2wqFNGYa8kVqEe-zN_JoHXZSro8H8-AtpkMcLJA,1891
16
+ nightshift/adapters/_process.py,sha256=2PrAVed86ewhfkjh3xRsBRcweE73eAsZSD9S99d_7ac,7040
17
+ nightshift/adapters/base.py,sha256=kVf5sXiwTKyIAhs-GKbkgMxUQNp7nuOS8Ukoki6Q11Y,5062
18
+ nightshift/adapters/claude_code.py,sha256=TEbaFXe7cuXUe5JpWyDeGOX2Hu4nOcnZoBtlAXh7VbI,13914
19
+ nightshift/adapters/codex.py,sha256=rmASV0MXrfvtL7feDdoYcxj3re46UyKsrjSCR-W_BZI,14668
20
+ nightshift/adapters/copilot.py,sha256=JMLhiPLVEhjY4GyRFC2aYn25YTSsfVkdoth-XSEph7c,2352
21
+ nightshift/prompts/code_review.md,sha256=TvOEQqXsFXkt8UCqiGitRAre_oUX9d9l2GGzV3bPpxU,1138
22
+ nightshift/prompts/dead_links.md,sha256=fZj4tKI4h4bVfqtUae6Ls1JO1V52KJFBKl0_0YO5598,1103
23
+ nightshift/prompts/deps_audit.md,sha256=LRj3oBLnR9DZDNyCIEK-nAgIntqXBWr8UzItMoGeiAM,1125
24
+ nightshift/prompts/docs_drift.md,sha256=P3y_1rI7qjbbvjZQIizRt5aMRC3Ju0mKGMnAfBL2w88,1061
25
+ nightshift/prompts/security_audit.md,sha256=LScoW9cth4MB4xaokPtMOCRjboA2-Y2XQ05YaNY8d2U,1095
26
+ nightshift_cli-0.1.0.dist-info/METADATA,sha256=7vMPTB5T5_XhkUeN0ux79D6SG7tSPG51M1yVsLs_Y0k,15930
27
+ nightshift_cli-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
28
+ nightshift_cli-0.1.0.dist-info/entry_points.txt,sha256=SX_Ru6Yvt-zk18NtePegg1D9gNGw1XCVd0CVzrss4nA,51
29
+ nightshift_cli-0.1.0.dist-info/licenses/LICENSE,sha256=EYqMCn5BztgpX_wxO_p1CUfliftVkcEYe-qCUqeLlK8,1073
30
+ nightshift_cli-0.1.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,2 @@
1
+ [console_scripts]
2
+ nightshift = nightshift.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Md. Kishor Morol
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.