trigger-tree 1.14.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,27 @@
1
+ """Shared runtime path resolution for trigger-tree hook entry points."""
2
+
3
+ import os
4
+ import subprocess
5
+
6
+
7
+ def project_root(cwd=None):
8
+ """Resolve one dataset root: explicit override, git root, Claude root, then cwd."""
9
+ explicit = os.environ.get("TT_PROJECT_DIR")
10
+ if explicit:
11
+ return explicit
12
+ claude_root = os.environ.get("CLAUDE_PROJECT_DIR")
13
+ working = cwd or claude_root or os.getcwd()
14
+ try:
15
+ root = subprocess.run(
16
+ ["git", "rev-parse", "--show-toplevel"],
17
+ cwd=working,
18
+ capture_output=True,
19
+ text=True,
20
+ timeout=2,
21
+ check=True,
22
+ ).stdout.strip()
23
+ if root:
24
+ return root
25
+ except (OSError, subprocess.SubprocessError):
26
+ pass
27
+ return claude_root or cwd or os.getcwd()
@@ -0,0 +1,76 @@
1
+ """Bounded, local audit of Markdown files covered by trigger-tree's watch regex."""
2
+
3
+ import os
4
+ import re
5
+
6
+ DEFAULT_LIMIT = 5000
7
+ SKIP_DIRS = {
8
+ ".git",
9
+ ".agents",
10
+ ".codex",
11
+ ".pytest_cache",
12
+ ".trigger-tree",
13
+ "node_modules",
14
+ "vendor",
15
+ "vendors",
16
+ "dist",
17
+ "build",
18
+ "__pycache__",
19
+ "tests",
20
+ "worktrees",
21
+ }
22
+
23
+
24
+ def scan_markdown(root, watch_regex, limit=DEFAULT_LIMIT):
25
+ """Return visited/Markdown/watched counts without following links or walking forever."""
26
+ pattern = re.compile(watch_regex)
27
+ visited = markdown = watched = 0
28
+ paths = []
29
+ capped = False
30
+ for current, directories, files in os.walk(root, followlinks=False):
31
+ directories[:] = [
32
+ name for name in directories if name not in SKIP_DIRS and not name.startswith(".venv")
33
+ ]
34
+ for name in files:
35
+ if visited >= limit:
36
+ capped = True
37
+ return {
38
+ "visited": visited,
39
+ "markdown": markdown,
40
+ "watched": watched,
41
+ "paths": paths,
42
+ "capped": capped,
43
+ }
44
+ visited += 1
45
+ if not name.lower().endswith((".md", ".markdown")):
46
+ continue
47
+ markdown += 1
48
+ relative = os.path.relpath(os.path.join(current, name), root).replace(os.sep, "/")
49
+ paths.append(relative)
50
+ watched += bool(pattern.search(relative))
51
+ return {
52
+ "visited": visited,
53
+ "markdown": markdown,
54
+ "watched": watched,
55
+ "paths": paths,
56
+ "capped": capped,
57
+ }
58
+
59
+
60
+ def suggested_regex(paths):
61
+ """Describe only observed Markdown locations, without inventing repository intent."""
62
+ roots = sorted({path.split("/", 1)[0] for path in paths if "/" in path})
63
+ files = sorted(path for path in paths if "/" not in path)
64
+ parts = []
65
+ if roots:
66
+ parts.append(
67
+ r"^(?:" + "|".join(re.escape(name) for name in roots) + r")/.*\.(?:md|markdown)$"
68
+ )
69
+ if files:
70
+ parts.append(r"^(?:" + "|".join(re.escape(name) for name in files) + r")$")
71
+ return "|".join(parts)
72
+
73
+
74
+ def is_poor_coverage(result):
75
+ total = result["markdown"]
76
+ return total > 0 and (result["watched"] == 0 or result["watched"] / total < 0.25)
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const value = process.argv[2] || "";
5
+ const colors = value.split(",").map((hex) => hex.trim());
6
+ if (colors.length < 2 || colors.some((hex) => !/^#[0-9a-f]{6}$/i.test(hex))) {
7
+ console.error("usage: validate_palette.js '#hex,#hex,…' --mode dark");
8
+ process.exit(2);
9
+ }
10
+
11
+ const rgb = (hex) => [1, 3, 5].map((at) => parseInt(hex.slice(at, at + 2), 16) / 255);
12
+ const simulations = {
13
+ normal: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
14
+ protan: [[0.567, 0.433, 0], [0.558, 0.442, 0], [0, 0.242, 0.758]],
15
+ deutan: [[0.625, 0.375, 0], [0.7, 0.3, 0], [0, 0.3, 0.7]],
16
+ tritan: [[0.95, 0.05, 0], [0, 0.433, 0.567], [0, 0.475, 0.525]],
17
+ };
18
+ const transform = (color, matrix) => matrix.map((row) => row.reduce((sum, n, i) => sum + n * color[i], 0));
19
+ const distance = (a, b) => Math.sqrt(a.reduce((sum, n, i) => sum + (n - b[i]) ** 2, 0)) * 255;
20
+ let failed = false;
21
+ for (let index = 1; index < colors.length; index += 1) {
22
+ const scores = Object.entries(simulations).map(([name, matrix]) => [name, distance(transform(rgb(colors[index - 1]), matrix), transform(rgb(colors[index]), matrix))]);
23
+ const minimum = Math.min(...scores.map(([, score]) => score));
24
+ console.log(`${colors[index - 1]} → ${colors[index]}: ${scores.map(([name, score]) => `${name} ${score.toFixed(1)}`).join(" · ")}`);
25
+ if (minimum < 28) failed = true;
26
+ }
27
+ console.log(failed ? "FAIL: adjacent step below CVD separation threshold 28" : "PASS: all adjacent steps meet CVD separation threshold 28");
28
+ process.exitCode = failed ? 1 : 0;
trigger_tree/cli.py ADDED
@@ -0,0 +1,52 @@
1
+ """Standalone `tt` console entry point dispatching to the bundled scripts."""
2
+
3
+ import runpy
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ SUBCOMMANDS = (
8
+ "doctor",
9
+ "log",
10
+ "report",
11
+ "setup",
12
+ "stats",
13
+ "suggestions",
14
+ "tips",
15
+ "uninstall",
16
+ "watch",
17
+ )
18
+ USAGE = f"usage: tt <{'|'.join(SUBCOMMANDS)}> [options]\n"
19
+
20
+
21
+ def script_path(name, package_dir=None):
22
+ """Prefer the wheel's bundled copy; fall back to the repository layout."""
23
+ package_dir = Path(__file__).parent if package_dir is None else Path(package_dir)
24
+ packaged = package_dir / "_scripts" / f"tt-{name}.py"
25
+ if packaged.is_file():
26
+ return packaged
27
+ return package_dir.resolve().parent / "scripts" / f"tt-{name}.py"
28
+
29
+
30
+ def main(argv=None):
31
+ argv = sys.argv[1:] if argv is None else list(argv)
32
+ if argv and argv[0] in ("-h", "--help"):
33
+ sys.stdout.write(USAGE)
34
+ return 0
35
+ if not argv or argv[0] not in SUBCOMMANDS:
36
+ sys.stderr.write(USAGE)
37
+ return 2
38
+ path = script_path(argv[0])
39
+ sys.argv = [str(path), *argv[1:]]
40
+ # `python3 script.py` puts the script's directory on sys.path; run_path does not.
41
+ directory = str(path.parent)
42
+ sys.path.insert(0, directory)
43
+ try:
44
+ runpy.run_path(str(path), run_name="__main__")
45
+ finally:
46
+ if directory in sys.path:
47
+ sys.path.remove(directory)
48
+ return 0
49
+
50
+
51
+ if __name__ == "__main__":
52
+ sys.exit(main())
@@ -0,0 +1,11 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [{"hooks": [{"type": "command", "command": "python3", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py", "--client", "claude"], "timeout": 5}]}],
4
+ "UserPromptSubmit": [{"hooks": [{"type": "command", "command": "python3", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py", "--client", "claude"], "timeout": 5}]}],
5
+ "SessionEnd": [{"hooks": [{"type": "command", "command": "python3", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py", "--client", "claude"], "timeout": 5}]}],
6
+ "PostToolUse": [
7
+ {"matcher": "Bash|Read|Glob|Grep|Skill|mcp__.*", "hooks": [{"type": "command", "command": "python3", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py", "--client", "claude"], "timeout": 5}]}
8
+ ],
9
+ "PostToolUseFailure": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "python3", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py", "--client", "claude"], "timeout": 5}]}]
10
+ }
11
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "description": "Collect local documentation-discovery telemetry without affecting Codex commands.",
3
+ "hooks": {
4
+ "SessionStart": [{"hooks": [{"type": "command", "command": "if command -v python3 >/dev/null 2>&1; then exec python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py\" --client codex; else exec python \"${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py\" --client codex; fi", "commandWindows": "py -3 \"%CLAUDE_PLUGIN_ROOT%\\scripts\\tt-codex-hook.py\" --client codex", "timeout": 5}]}],
5
+ "UserPromptSubmit": [{"hooks": [{"type": "command", "command": "if command -v python3 >/dev/null 2>&1; then exec python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py\" --client codex; else exec python \"${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py\" --client codex; fi", "commandWindows": "py -3 \"%CLAUDE_PLUGIN_ROOT%\\scripts\\tt-codex-hook.py\" --client codex", "timeout": 5}]}],
6
+ "PostToolUse": [{"matcher": "Bash|Read|Glob|Grep|mcp__.*", "hooks": [{"type": "command", "command": "if command -v python3 >/dev/null 2>&1; then exec python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py\" --client codex; else exec python \"${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py\" --client codex; fi", "commandWindows": "py -3 \"%CLAUDE_PLUGIN_ROOT%\\scripts\\tt-codex-hook.py\" --client codex", "timeout": 5}]}],
7
+ "Stop": [{"hooks": [{"type": "command", "command": "if command -v python3 >/dev/null 2>&1; then exec python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py\" --client codex; else exec python \"${CLAUDE_PLUGIN_ROOT}/scripts/tt-codex-hook.py\" --client codex; fi", "commandWindows": "py -3 \"%CLAUDE_PLUGIN_ROOT%\\scripts\\tt-codex-hook.py\" --client codex", "timeout": 5}]}]
8
+ }
9
+ }
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: trigger-tree
3
+ Version: 1.14.0
4
+ Summary: See which project docs your AI coding agent actually discovers — local, zero-token documentation telemetry.
5
+ Project-URL: Homepage, https://hedde.github.io/trigger_tree/
6
+ Project-URL: Repository, https://github.com/Hedde/trigger_tree
7
+ Project-URL: Changelog, https://github.com/Hedde/trigger_tree/blob/main/CHANGELOG.md
8
+ Author: Hedde van der Heide
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agents,claude-code,codex,docs-as-code,documentation,observability,telemetry
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Documentation
23
+ Classifier: Topic :: Software Development :: Documentation
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+
27
+ # 🌳 trigger-tree
28
+
29
+ > **See which project docs your AI actually discovers.**
30
+
31
+ [![CI](https://github.com/Hedde/trigger_tree/actions/workflows/ci.yml/badge.svg)](https://github.com/Hedde/trigger_tree/actions/workflows/ci.yml)
32
+ [![coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FHedde%2Ftrigger_tree%2Fbadges%2Fcoverage.json)](https://github.com/Hedde/trigger_tree/actions/workflows/ci.yml)
33
+ [![release](https://img.shields.io/github/v/release/Hedde/trigger_tree?label=release)](https://github.com/Hedde/trigger_tree/releases/latest)
34
+ [![PyPI](https://img.shields.io/pypi/v/trigger-tree?label=pypi)](https://pypi.org/project/trigger-tree/)
35
+ [![platforms](https://img.shields.io/badge/platforms-macOS%20·%20Linux%20·%20Windows-lightgrey.svg)](docs/platform-support.md)
36
+ [![docs health](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FHedde%2Ftrigger_tree%2Fbadges%2Fdocs-health.json)](docs/heat-model.md)
37
+
38
+ <p align="center"><a href="https://hedde.github.io/trigger_tree/"><img src="https://raw.githubusercontent.com/Hedde/trigger_tree/main/docs/assets/dashboard.png" alt="trigger-tree mock dashboard showing documentation reads, searches, heat bars, and prompt browsing" width="900"></a></p>
39
+
40
+ - Local and dependency-free: no cloud, analytics, or model tokens.
41
+ - Separate heat, lifetime reads, searches, and untouched paths instead of guessing intent.
42
+ - Reduce the evidence to an A–F documentation health grade when detail is unnecessary.
43
+
44
+ Documentation steers an AI coding assistant toward your team’s patterns and guardrails. But **a rule that is never read protects nothing**. trigger-tree records discovery evidence so you can improve the routes without pretending a read proves understanding.
45
+
46
+ ## Quick start
47
+
48
+ | Claude Code | Codex |
49
+ |---|---|
50
+ | `/plugin marketplace add Hedde/trigger_tree`<br>`/plugin install trigger-tree@trigger-tree`<br>`/reload-plugins`<br>`/tt watch demo`<br>`/tt setup` · `/tt doctor`<br>Work normally, then `/tt insights` | `codex plugin marketplace add Hedde/trigger_tree`<br>`codex plugin install trigger-tree`<br>Restart Codex<br>Ask it to run `python3 "$PLUGIN_ROOT/scripts/tt-watch.py" --demo`<br>Use the bundled trigger-tree skill for setup, doctor, and insights |
51
+
52
+ The Claude `/tt` skill is explicitly user-triggered. Codex installs the equivalent skill and lifecycle hooks through its plugin marketplace.
53
+
54
+ Prefer a standalone CLI — for CI, git-hook ingestion, or dashboards without a plugin?
55
+ `pipx install trigger-tree`, then `tt doctor`, `tt watch --demo`, `tt stats`.
56
+
57
+ Prompt logging defaults to a recognizable, gitignored local preview of at most 200
58
+ characters as soon as the plugin is installed, even if setup is never run. `/tt setup`
59
+ asks whether to keep `truncate` or switch future events to `hash` or `off`.
60
+
61
+ ## Who gets what?
62
+
63
+ | You are… | trigger-tree gives you… |
64
+ |---|---|
65
+ | Senior developer | File/folder heat, search evidence, router gaps, and prompt-level browsing |
66
+ | Tech lead | Trends, task clusters, protected-context review, and evidence-backed fixes |
67
+ | Product owner | One honest A–F docs-health signal, provisional until measurement matures |
68
+
69
+ ## Commands
70
+
71
+ | Command | Result |
72
+ |---|---|
73
+ | `/tt watch demo` | Instant synthetic dashboard; no telemetry required |
74
+ | `/tt setup [truncate\|hash\|off]` | Wire the repo and choose prompt privacy |
75
+ | `/tt doctor` | Check hooks, liveness, scope, privacy, and statusline wiring |
76
+ | `/tt status` | Current heat, lifetime reads, and untouched paths |
77
+ | `/tt watch` | Live mock-TUI dashboard with prompt browsing and sorting |
78
+ | `/tt insights` | Deterministic analysis plus a local HTML report |
79
+ | `/tt suggestions` | Up to five evidence-backed routing improvements |
80
+ | `/tt badge` | Write a public-safe docs-health endpoint JSON |
81
+ | `/tt note <text>` | Add a local timeline annotation |
82
+ | `/tt uninstall` | Remove wiring without deleting telemetry |
83
+
84
+ Search telemetry is a conservative lower bound; see [measurement boundaries](docs/heat-model.md).
85
+
86
+ ## How it works
87
+
88
+ 1. **Hooks log shell-side** to the gitignored `.trigger-tree/history.jsonl`; failures never interrupt the coding session.
89
+ 2. **A deterministic aggregator computes every metric** with Python’s standard library; the model interprets but never counts.
90
+ 3. **Discovery remains model-driven**: trigger-tree measures your routers and reads without injecting context or changing routing.
91
+
92
+ ## Where it fits
93
+
94
+ | Category | Question answered |
95
+ |---|---|
96
+ | Token/trace observability (Langfuse, Arize, W&B) | What did the model call, spend, and produce? |
97
+ | Documentation linters | Is documentation structurally or stylistically valid? |
98
+ | trigger-tree | Which local project docs did the coding assistant actually discover? |
99
+
100
+ The categories complement each other. trigger-tree does not evaluate answer quality or claim that a read caused an outcome.
101
+
102
+ ## Learn more
103
+
104
+ [Documentation router](docs/README.md) · [Dashboard](docs/dashboard.md) · [Heat model](docs/heat-model.md) · [Configuration](docs/configuration.md) · [Privacy](docs/privacy.md) · [Glossary](docs/glossary.md) · [FAQ](docs/faq.md) · [Website](https://hedde.github.io/trigger_tree/) · [Changelog](CHANGELOG.md)
105
+
106
+ MIT © Hedde van der Heide
@@ -0,0 +1,27 @@
1
+ trigger_tree/__init__.py,sha256=P9roqhukFAVY8OPVrGOmoCmYxuPDNDodFSRrBYPT_yg,93
2
+ trigger_tree/cli.py,sha256=3jewzuhl2KEt7HZkHrv-2Smbp155LWwH9_YoUmBXOI4,1424
3
+ trigger_tree/_scripts/tt-codex-hook.py,sha256=8My7U_o8QXBA_fuwx--upv6vGMGtVQvaBxngiOQuIB0,3811
4
+ trigger_tree/_scripts/tt-config.sh,sha256=h5zqS6V6pWdrb4ya54TPIi30PzLCR-ptRFEcqxp54ag,1778
5
+ trigger_tree/_scripts/tt-doctor.py,sha256=CObjQQ5IRvZZlXWL-kn14YyauXJMAg__rBMr4sbofEQ,11007
6
+ trigger_tree/_scripts/tt-log.py,sha256=gDDV6qseZr0KkTLoznDtiwMqfJIjf51RXJfFnOL1I0s,23560
7
+ trigger_tree/_scripts/tt-open.sh,sha256=Ha_I3v6Tl5wgx5vfFqTq8OplvmwLAxWhNH8l98dQACE,5660
8
+ trigger_tree/_scripts/tt-publish-badge.sh,sha256=Ks62PoEb_ADznOfRkd8PJWGqs-s6AOQksypHrxqiLzc,902
9
+ trigger_tree/_scripts/tt-report.py,sha256=3w0qFyD9s0lABbGIvR7RQFvX5B6vIj89Yr7qFya85s8,28755
10
+ trigger_tree/_scripts/tt-setup.py,sha256=jgcnHw8mVPfxqUXp3YUuACcucXAput9asua-n_fcMzQ,9914
11
+ trigger_tree/_scripts/tt-shell-capture.sh,sha256=kT7S7JPT2SAdbUF-Z3yz4FKMA76ezlE7YUfQjA8m6U0,1754
12
+ trigger_tree/_scripts/tt-stats.py,sha256=_KkFoVTqJzlxzY-tFSicu1it-Rsmt8DmGJx92FGvfyo,35035
13
+ trigger_tree/_scripts/tt-statusline.py,sha256=dnBE4GVfJQ4bZJFlbvkxS9-2WlteAfos9gLTzn4ZmBo,4948
14
+ trigger_tree/_scripts/tt-suggestions.py,sha256=ijgnsM-4xgngS4RePPjxMXWJCno6D3eMpiuyV-Cs2Gc,5058
15
+ trigger_tree/_scripts/tt-tips.py,sha256=0-_w9RLFqqfuIeYS1GWcLBlIjc6mxwrE3o9U1BmYfkE,3768
16
+ trigger_tree/_scripts/tt-uninstall.py,sha256=nu02WOTe8hx4nUCZ26OkkcGzjzSim3ZOVy2gUPX7Yfw,1984
17
+ trigger_tree/_scripts/tt-watch.py,sha256=crTfhspjTeztOrFWgAQtzZiivlwgx8JrKT_S0SwOEYQ,41355
18
+ trigger_tree/_scripts/tt_runtime.py,sha256=A03j0N3f2_5wfHEOyixfJc7wntSnyZlRzuJyGZZ2tgQ,807
19
+ trigger_tree/_scripts/tt_scope.py,sha256=DcY6DH5baterI6Qa3C7M6UekErjsRGECdEtjhzoyotk,2317
20
+ trigger_tree/_scripts/validate_palette.js,sha256=pRtwOquqLhjMdNkGzU0BF-azvWdUMn7c8Kh2ajuZ3YQ,1526
21
+ trigger_tree/hooks/claude-hooks.json,sha256=RoHiu1sXzYmswtwaOLckpzdlcbMUT5Zx_vI7GUJnMT4,976
22
+ trigger_tree/hooks/hooks.json,sha256=Rzgahpollpbv3GCLan7R2y5oj67jR1w7AOX3BXU-vso,1715
23
+ trigger_tree-1.14.0.dist-info/METADATA,sha256=2tSxuFRsBRrPSb0qp0hW_5deIpYRgbOfVrWKUNyaYeQ,6425
24
+ trigger_tree-1.14.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
25
+ trigger_tree-1.14.0.dist-info/entry_points.txt,sha256=uTp1hl7A34dKWl8jSLnGrm9HPKW_UcguKTZ6U8b0i2A,45
26
+ trigger_tree-1.14.0.dist-info/licenses/LICENSE,sha256=IWwYShDdxsqaviOxxvmn6BEz0mLTTWVZZUgGat6khXE,1076
27
+ trigger_tree-1.14.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
+ tt = trigger_tree.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hedde van der Heide
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.