whosaid 1.1.0__tar.gz

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,76 @@
1
+ # Generated by /cbm-index — keeps the codebase-memory graph small and queries fast.
2
+ # Edit freely; this file is yours.
3
+
4
+ # VCS
5
+ .git/
6
+ .hg/
7
+ .svn/
8
+
9
+ # Build output
10
+ dist/
11
+ build/
12
+ out/
13
+ target/
14
+ .next/
15
+ .nuxt/
16
+ .svelte-kit/
17
+ .output/
18
+
19
+ # Caches
20
+ .cache/
21
+ .parcel-cache/
22
+ .turbo/
23
+ .swc/
24
+ tmp/
25
+ temp/
26
+
27
+ # Dependencies
28
+ node_modules/
29
+ bower_components/
30
+ .pnpm-store/
31
+ vendor/
32
+
33
+ # Coverage / reports
34
+ coverage/
35
+ .nyc_output/
36
+ htmlcov/
37
+ .coverage
38
+ *.lcov
39
+
40
+ # Editor / OS
41
+ .idea/
42
+ .vscode/
43
+ .DS_Store
44
+ *.swp
45
+
46
+ # Logs
47
+ *.log
48
+
49
+ # Minified / sourcemaps (low signal for graph)
50
+ *.min.js
51
+ *.min.css
52
+ *.bundle.js
53
+ *.map
54
+
55
+ # Lockfiles (not source code, but bulky)
56
+ pnpm-lock.yaml
57
+ yarn.lock
58
+ package-lock.json
59
+ poetry.lock
60
+ uv.lock
61
+ Gemfile.lock
62
+ composer.lock
63
+
64
+ # Python
65
+ __pycache__/
66
+ *.pyc
67
+ *.pyo
68
+ *.egg-info/
69
+ .pytest_cache/
70
+ .mypy_cache/
71
+ .ruff_cache/
72
+ .tox/
73
+ .venv/
74
+ venv/
75
+ env/
76
+ *.whl
@@ -0,0 +1,43 @@
1
+ name: prxref review
2
+
3
+ # pull_request_target runs in the BASE repository context, so it has access to
4
+ # secrets and a write token even for fork PRs. This workflow never checks out
5
+ # or executes the pull request's code: prxref fetches the diff over the GitHub
6
+ # API from the PR URL alone, running from PyPI.
7
+ on:
8
+ pull_request_target:
9
+ types: [opened, synchronize, reopened]
10
+
11
+ permissions:
12
+ contents: read
13
+ pull-requests: write
14
+
15
+ jobs:
16
+ review:
17
+ name: prxref review
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+ # prxref is non-blocking by design (exits 0 on all network/LLM/forge errors),
21
+ # but continue-on-error provides belt-and-braces protection for CI continuity.
22
+ continue-on-error: true
23
+
24
+ steps:
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+ with:
28
+ python-version: "3.12"
29
+
30
+ - name: Run prxref review
31
+ env:
32
+ PRXREF_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
+ PRXREF_LLM_BACKEND: http
34
+ PRXREF_LLM_BASE_URL: ${{ vars.PRXREF_LLM_BASE_URL }}
35
+ PRXREF_LLM_API_KEY: ${{ secrets.PRXREF_LLM_API_KEY }}
36
+ PRXREF_LLM_MODELS: ${{ vars.PRXREF_LLM_MODELS }}
37
+ PRXREF_LLM_REASONING_EFFORT: ${{ vars.PRXREF_LLM_REASONING_EFFORT }}
38
+ # Per-WORKER-CHUNK completion budget, not a per-run one. Needs the
39
+ # literal fallback because an unset repository variable renders as
40
+ # the empty string and prxref falls back to 4096, which truncates
41
+ # reasoning models before the findings JSON is emitted.
42
+ PRXREF_LLM_MAX_TOKENS: ${{ vars.PRXREF_LLM_MAX_TOKENS || '32000' }}
43
+ run: uvx prxref review --pr-url "${{ github.event.pull_request.html_url }}" -v
@@ -0,0 +1,11 @@
1
+ voices/*
2
+ recordings/*
3
+ !voices/.gitkeep
4
+ !recordings/.gitkeep
5
+ .DS_Store
6
+ *.log
7
+ __pycache__/
8
+ .codex/
9
+ .claude/
10
+ # generated codebase-memory atlas output (analysis artifact, not source)
11
+ .cbm-atlas/
@@ -0,0 +1,260 @@
1
+ # Changelog
2
+
3
+ All notable changes to whosaid are documented here. This project adheres to
4
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the format of
5
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - **PyPI packaging (`pyproject.toml`).** The `lib/` modules now ship as the importable `whosaid`
12
+ package (PEP 621, hatchling; wheel renames `lib/` → `whosaid/`), with a `whosaid-mcp` console
13
+ script that runs the MCP server (`python -m`-style entry `whosaid.mcp_server:main`) and the MCP
14
+ registry marker (`io.github.sblattj/whosaid`) in the README. Runtime deps are declared
15
+ (`mlx-whisper`, `mcp`, `numpy`, `sherpa-onnx`); ffmpeg/uv remain system prerequisites from
16
+ `whosaid setup`, and the full CLI is still installed from a checkout via `whosaid install`.
17
+ `pyproject.toml` carries the version — bump it together with `WHOSAID_VERSION` in the `whosaid`
18
+ script and `__version__` in `lib/mcp_server.py` at release time. `[tool.uv] managed = false`
19
+ keeps `uv run` in the checkout working exactly as before (ephemeral `--with` environments; no
20
+ project sync, no `.venv`, no lockfile).
21
+ - **`--expected-speakers` registry-anchored diarization (GitHub issue #1, part 4).** `whosaid <audio> --expected-speakers Alice,Bob` (comma-separated and repeatable; also on `ingest`, and as `expected_speakers` on the `whosaid_transcribe` MCP tool) anchors clustering to the enrolled voiceprints of the people you expect: each turn at cosine ≥ `--anchor-threshold` (default `0.70`, env `WHOSAID_ANCHOR_THRESHOLD`) is pinned to that person, the residual turns are clustered into new speakers as before, and a listed person who never speaks is dropped — so a recurring team with varying attendance no longer needs an exact `--speakers N` (which merges distinct people when fewer show up) or bare auto-detect (which over-segments and then leaves a major speaker unmatched). Unknown names are fatal and list the known voices. Per-anchor results land in the sidecar under `anchors` and in `registry_matches` with `pass: "anchor"`; the flag forces the chunked diarization path at any length, since anchoring needs per-turn voiceprints.
22
+ - **Temp-directory install guard (GitHub issue #10).** `whosaid install` (and `./bootstrap.sh`,
23
+ before its multi-minute model downloads) now refuse to install from a checkout under `/tmp`,
24
+ `/private/tmp`, `/var/tmp`, or `$TMPDIR` unless `--force` is given, since the installed symlink
25
+ would silently dangle once the OS cleans that directory up. `whosaid doctor` now also reports a
26
+ dangling or foreign install symlink so a "command not found" regression points at the real cause.
27
+ - **Match-threshold flag, machine-readable match confidence, source metadata (GitHub issue #1, parts 2–3).**
28
+ `whosaid <audio> --match-threshold F` (alias `--ref-threshold`, env `WHOSAID_MATCH_THRESHOLD`) now
29
+ passes the registry/reference match gate through from the CLI, `ingest`, `relabel` and the MCP
30
+ tools, and its default is raised from `0.40` to `0.50` — real meeting audio produced wrong
31
+ assertions in the 0.40–0.53 band, while genuine matches score far higher. Clusters below the
32
+ threshold keep their `SPEAKER_NN` label. Every naming decision, near-misses included, is written
33
+ to `<base>.diarization.json` as `registry_matches`
34
+ (`{cluster, name, similarity, threshold, matched, pass}`), refreshed by `relabel --auto`, and the
35
+ sidecar also gains `source` (`path`, `duration_seconds`, `creation_time`) so no separate `ffprobe`
36
+ is needed for per-meeting timestamps.
37
+ - **`whosaid enroll --from FILE` (GitHub issue #1, part 1).** Enroll a voice from an existing
38
+ recording instead of the mic: `--ss`/`--t`/`--to` cut a time window (seconds or `M:SS`/`H:MM:SS`),
39
+ the same ≥15s/non-silent check runs on the extracted clip, and `--force` allows overwriting an
40
+ existing `voices/<Name>.wav`.
41
+ - **`whosaid samples <base>` and the identity-mechanisms doc (GitHub issue #1, nice-to-haves).**
42
+ `whosaid samples <base> [-o DIR] [--audio FILE] [--per-speaker N] [--seconds S] [--json]` exports
43
+ one short representative WAV per speaker cluster (the longest segment, clamped to `--seconds`,
44
+ default 8) so you can listen and confirm an identity before trusting an auto-label or enrolling —
45
+ replacing the by-hand `ffmpeg` cut issue #1 described. Also exposed as the `whosaid_samples` MCP
46
+ tool. A new README section, "Speaker identity: enrollment clips vs. the registry", documents how
47
+ `WHOSAID_VOICE_REFS` enrollment clips and the speaker registry (`relabel`/`--save-speaker`) differ
48
+ and which one names a given cluster.
49
+ - **Meeting workspaces (GitHub issue #2).** A dated, auditable home for a recurring meeting
50
+ series (`lib/workspace.py`): every recording transcribed into a `YYYY-MM-DD-HHMM` folder,
51
+ per-meeting action items, and one roll-up across the whole workspace — still entirely offline.
52
+ - **`whosaid ingest` — dated, idempotent batch folders.** Transcribes a batch into folders named
53
+ from each recording's container `creation_time` (rendered in `--tz`, mtime fallback),
54
+ idempotent by source sha256, passing through all transcribe flags.
55
+ - **Pluggable action-items hook.** `--hook CMD` (or the `WHOSAID_ACTION_ITEMS_HOOK` environment
56
+ variable) receives the speaker-labeled transcript on stdin plus `WHOSAID_SPEAKERS` /
57
+ `WHOSAID_TRANSCRIPT_PATH`, and its stdout becomes the meeting's `action-items.md`. With no hook
58
+ a skeleton is written instead, so the default stays fully offline.
59
+ - **`whosaid roll-up` — coverage index with a nothing-missing audit.** `_INDEX.md` lists one row
60
+ per meeting (created, duration, transcribed/diarized/action-items) and flags orphan directories
61
+ and stale manifest entries, alongside a recurring-topics section.
62
+ - **Living, deduplicated action-item corpus.** With `--action-items`, `_ACTION-ITEMS.md` folds
63
+ every meeting's items into stable `AI-001` ids (never renumbered) with `first_seen` /
64
+ `last_seen`, occurrence lists, and open/ongoing/resolved statuses that survive re-runs.
65
+ Incremental and append-only by default (`--rebuild` to reset); state is plain JSON
66
+ (`_workspace.json`, `_action-items.json`) that is safe to hand-edit.
67
+ - **`whosaid version` / `--version` (GitHub issue #9).** Prints the installed version (and, from
68
+ a git checkout, a `git describe` suffix); also exposed as the `version` field in the MCP
69
+ `whosaid_doctor` tool's report.
70
+
71
+ ### Fixed
72
+
73
+ - **Phantom speaker clusters on long recordings (GitHub issues #5, #6, #7, #8, #11).** Four
74
+ related fixes so a long meeting no longer fragments into duplicate/unidentified speakers:
75
+ - **Absorb pass.** After the registry one-best and `--ref` passes, every still-unnamed cluster
76
+ whose centroid cosine to a known voice (registry entry or `--ref` clip) is `>=`
77
+ `--absorb-threshold` (default `0.85`, env `WHOSAID_ABSORB_THRESHOLD`) is folded into that
78
+ person. A person split across several clusters is named on all of them, and the speaker cards
79
+ now render **one card per name** with the combined turns/talk time (was one card per cluster).
80
+ - **`--ref` no longer double-names.** The `--ref` pass only considers still-unnamed clusters and
81
+ skips any name the registry already assigned, so an enrolled voice plus a registry entry for the
82
+ same person can't produce two cards for them.
83
+ - **Auto speaker-count cap guard.** When farthest-first speaker-count estimation saturates at the
84
+ cap (20), it is re-estimated with progressively lower merge thresholds until the count drops
85
+ below the cap, instead of handing k-means a `k` of 20 that shatters real voices. The
86
+ over-segmentation WARN also now fires when the final count equals the cap.
87
+ - **Auto speaker-count estimation (GitHub issue #5, #6 header warning, #1 min/max speakers).**
88
+ Replaces the farthest-first count (and its cap-retry ladder) with average-linkage
89
+ agglomerative clustering of the per-turn voiceprints, cut at cosine `0.58` (env
90
+ `WHOSAID_COUNT_THRESHOLD`), using the nearest-neighbour-chain algorithm so it stays O(n^2):
91
+ 1000 turns estimate in **0.009 s**. The cap of 20 is now a **bound, not a target** — an
92
+ estimate that lands on it is treated as a failed estimate rather than a result.
93
+ - Measured on a purpose-built 17.8-minute, 6-voice, 48-turn synthetic meeting (macOS `say`,
94
+ 65-72 embedded turns): raw `say` audio is too clean to fragment, so base and new both
95
+ return the true 6. Re-mixed with 8 per-turn channel profiles (band-limiting, room/headset
96
+ EQ, level offsets, pink noise) the true count is still 6, base returns 6, and the new
97
+ estimator returns **6** — an earlier cut of 0.45, calibrated from the 0.6-0.8 same-speaker
98
+ figure quoted in the issue, returned 5 and was corrected by measurement.
99
+ - Real per-turn TitaNet-small similarity through this pipeline is **intra-speaker ~0.90,
100
+ inter-speaker ~0.25** (not the 0.6-0.8 the issue assumes, which is the low tail); k=6 holds
101
+ for any cut in `[0.55, 0.61]` on the channel-varied fixture and `[0.44, 0.61]` on the clean
102
+ one, and 0.58 is the midpoint of the intersection.
103
+ - **`--min-speakers N` / `--max-speakers N`** (issue #1) clamp the auto estimate on
104
+ `whosaid`, the diarizer and the `whosaid_transcribe` MCP tool; `--max-speakers` also lowers
105
+ the cap, `--speakers N` still forces an exact count, and `min > max` is rejected. The
106
+ whole-file (<15 min) path passes an exact count only when `min == max` and otherwise
107
+ reports the range as unenforced, since sherpa's `FastClustering` has no notion of a range.
108
+ - **The count warning now reaches the artifact (issue #6).** When the count is untrustworthy
109
+ a `# WARNING: ...` line is written into `<base>.speaker-cards.txt` directly under the count
110
+ line, and `count_warning` plus a `count_estimate` record (`method`, `threshold`, `k`,
111
+ `raw_k`, `cap`, `min`, `max`, `saturated`) appear in `<base>.diarization.json`, in the JSON
112
+ on stdout, and in the MCP result.
113
+ - **Registry entries computed with a different embedding model no longer mis-match** in
114
+ `relabel --auto`: candidate voiceprints are filtered to the sidecar's own embedding model.
115
+
116
+ ### Added
117
+
118
+ - **`whosaid relabel <base> --auto` — re-apply naming with no re-diarization.** Reloads the cached
119
+ `<base>.diarization.json`, re-runs registry matching + the absorb pass, and rewrites
120
+ `<base>.speakers.txt` / `<base>.speaker-cards.txt` (and the sidecar's names). Picks up voices you
121
+ enrolled after the transcript was made and merges phantom splits. Accepts the meeting-workspace
122
+ layout (`base` = `transcript`). The MCP `whosaid_relabel` tool gains an `auto` parameter.
123
+ - **`--absorb-threshold F` transcribe/relabel flag** (env `WHOSAID_ABSORB_THRESHOLD`, default
124
+ `0.85`) controlling the absorb pass above.
125
+
126
+ ## [1.1.0] — 2026-08-18
127
+
128
+ ### Added
129
+
130
+ - **MCP server (`whosaid mcp`).** A stdio MCP (Model Context Protocol) server (`lib/mcp_server.py`,
131
+ built on the official `mcp` Python SDK's `FastMCP`) exposes whosaid to AI agents. Launched via
132
+ `whosaid mcp`, using the same ephemeral `uv run --with "mcp[cli]"` pattern as the rest of
133
+ whosaid — no persistent install.
134
+ - **Five tools**, all prefixed `whosaid_`: `whosaid_transcribe`, `whosaid_relabel`,
135
+ `whosaid_list_speakers` (read-only), `whosaid_doctor` (read-only), and
136
+ `whosaid_enroll_from_file`. Every tool shells the existing `whosaid` CLI rather than
137
+ reimplementing the pipeline, so the MCP surface and the CLI can't drift apart.
138
+ - **`whosaid://guide` resource** — an on-demand deep reference (full flag/env list, the long-audio
139
+ parallel path, the cosine-match threshold) an agent can read without it bloating every tool's
140
+ always-loaded description.
141
+
142
+ ## [1.0.2] — 2026-08-18
143
+
144
+ Documentation-accuracy patch — no change to the transcription or diarization
145
+ pipeline. A `/cbm-atlas` architecture audit confirmed the README already matches
146
+ the code; the drift was in the design doc, now corrected.
147
+
148
+ ### Documentation
149
+
150
+ - **`docs/design.md` refreshed to the current architecture.** The default
151
+ speaker-embedding model is corrected to NeMo TitaNet-small (it had named
152
+ 3D-Speaker ERes2Net, now listed only as an opt-in `DIARIZE_EMB_NAME`
153
+ alternative). Added the `install` and `relabel` subcommands, the persistent
154
+ local speaker registry, `.speaker-cards.txt`, the `.diarization.json` sidecar,
155
+ and the parallel long-audio path so the doc matches the implementation.
156
+ - **README wording tightened.** The long-audio parallel path now says it
157
+ "recovers the same speakers" as a single-pass run rather than "the same
158
+ result" — the whole-file and chunked paths use different clustering algorithms,
159
+ so bit-identical output isn't guaranteed.
160
+
161
+ ### Build
162
+
163
+ - `bootstrap.sh`'s disk-space check now distinguishes the ~4 GB recommended free
164
+ space from the ~1.5 GB Whisper model download.
165
+ - The generated `/cbm-atlas` output directory (`.cbm-atlas/`) is now gitignored.
166
+
167
+ ## [1.0.1] — 2026-08-17
168
+
169
+ Documentation and test-coverage patch — no change to the transcription or
170
+ diarization pipeline.
171
+
172
+ ### Documentation
173
+
174
+ - **README tuned for discoverability.** An above-the-fold summary now names the
175
+ terms people search for — speaker diarization, Whisper speech-to-text, "who
176
+ spoke when," offline/on-device, voice-based speaker recognition — and the
177
+ comparison section opens with a "MacWhisper / whisperX / aTrain alternative"
178
+ framing.
179
+
180
+ ### Tests
181
+
182
+ - **Expanded `test/e2e.sh` coverage.** Asserts every output format and artifact
183
+ from a run (`.srt`, `.vtt`, `.tsv`, `.json`, `.rttm`, the speaker-cards file,
184
+ and the `.diarization.json` sidecar); smoke-tests `whosaid doctor`'s
185
+ embedding-model and registry report; and exercises `--no-diarize` (plain
186
+ transcript, no `.speakers.txt`).
187
+
188
+ ## [1.0.0] — 2026-08-17
189
+
190
+ First stable release. whosaid is local, speaker-attributed transcription for
191
+ Apple Silicon: it turns an audio file into a transcript where every turn is
192
+ attributed to a person — who said what — with nothing ever leaving your Mac.
193
+
194
+ ### Highlights
195
+
196
+ - **Speaker-attributed transcripts.** MLX Whisper (Metal GPU) transcribes,
197
+ sherpa-onnx diarization (CPU) finds who spoke when, and the two are merged into
198
+ `<base>.speakers.txt` with per-turn speaker labels and timestamps.
199
+ - **Names speakers by voice.** A one-time ~45s enrollment (`whosaid enroll`)
200
+ teaches whosaid your voice, so your turns read as your name instead of
201
+ `SPEAKER_00`.
202
+ - **Persistent cross-meeting speaker identity.** Identify someone once with
203
+ `whosaid relabel` and their voiceprint is saved to a private local registry
204
+ (`~/.config/whosaid/speakers.json`), so they are auto-named in every future
205
+ transcript — a persistence most transcription tools don't offer.
206
+ - **Tells you how many people spoke.** The distinct-speaker count is
207
+ auto-detected and reported up front, with per-speaker turn counts and talk
208
+ time.
209
+ - **Speaker cards.** For each speaker, `<base>.speaker-cards.txt` holds a few
210
+ representative snippets so you can read a couple of lines and know who each
211
+ cluster is — then name them with `whosaid relabel`.
212
+ - **Fast on long recordings.** Audio over ~15 min is diarized in parallel time
213
+ windows and stitched back into consistent speakers by a single global
214
+ clustering pass over every turn's voiceprint — several times faster, with the
215
+ same result as a single-pass run.
216
+ - **No accounts, no API keys, no Hugging Face token.** Every model comes from an
217
+ open, ungated source and is cached locally after the first download.
218
+
219
+ ### Commands
220
+
221
+ - `whosaid <audio>…` — transcribe + diarize + label one or more files.
222
+ - `whosaid enroll [Name]` — record ~45s and name a voice.
223
+ - `whosaid record [--label L]` — capture from the mic, then transcribe.
224
+ - `whosaid relabel <base> SPEAKER_02=Name …` — name clusters from the speaker
225
+ cards and persist their voiceprints (no re-transcription).
226
+ - `whosaid doctor` — read-only environment / model / registry report.
227
+ - `whosaid setup` (`./bootstrap.sh`) — dependency check + model pre-download.
228
+ - `whosaid install` — install/update the `~/.local/bin/whosaid` command symlink.
229
+
230
+ ### Notes
231
+
232
+ - **The default speaker-embedding model is NeMo TitaNet-small** (English-native,
233
+ ~2.5× faster than ERes2Net in sherpa's benchmark). Set `DIARIZE_EMB_NAME` to
234
+ select the ERes2Net (en) or `…zh-cn…` (Mandarin) models from the same release.
235
+ - **`--chunk-seconds` is honored at any length.** Auto-chunking still only engages
236
+ past ~15 min, but passing an explicit `--chunk-seconds` forces the parallel
237
+ path on shorter audio too.
238
+ - **`whosaid doctor`** reports the active speaker-embedding model and the local
239
+ registry path + voiceprint count.
240
+ - **Hallucination-hardened transcription.** whosaid calls the `mlx-whisper`
241
+ library directly with the temperature-fallback ladder enabled,
242
+ `condition_on_previous_text` off, and a hallucination-silence threshold — the
243
+ configuration that avoids Whisper's repetition-collapse on long audio.
244
+ - **Output files:** `.txt`, `.srt`, `.vtt`, `.tsv`, `.json` (transcription), plus
245
+ `.rttm`, `.speakers.txt`, `.speaker-cards.txt`, and a `.diarization.json`
246
+ sidecar that makes `relabel` instant.
247
+ - **Privacy by construction:** audio, text, and voice embeddings never leave the
248
+ machine; the speaker registry lives outside the repo and is never uploaded.
249
+
250
+ ### Requirements
251
+
252
+ - An Apple Silicon Mac (MLX runs on the GPU via Metal); macOS.
253
+ - `ffmpeg` and `uv` (Homebrew). Python is used only through ephemeral `uv`
254
+ environments — no persistent install is left behind.
255
+
256
+ [Unreleased]: https://github.com/sblattj/whosaid/compare/v1.1.0...HEAD
257
+ [1.1.0]: https://github.com/sblattj/whosaid/releases/tag/v1.1.0
258
+ [1.0.2]: https://github.com/sblattj/whosaid/releases/tag/v1.0.2
259
+ [1.0.1]: https://github.com/sblattj/whosaid/releases/tag/v1.0.1
260
+ [1.0.0]: https://github.com/sblattj/whosaid/releases/tag/v1.0.0
whosaid-1.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stephen Blatt
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.