swarm-test 0.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.
Files changed (29) hide show
  1. swarm_test-0.1.0/.github/workflows/ci.yml +95 -0
  2. swarm_test-0.1.0/.gitignore +41 -0
  3. swarm_test-0.1.0/LICENSE +21 -0
  4. swarm_test-0.1.0/PKG-INFO +248 -0
  5. swarm_test-0.1.0/README.md +203 -0
  6. swarm_test-0.1.0/examples/areengine_swarm_test.py +254 -0
  7. swarm_test-0.1.0/examples/research_crew.py +307 -0
  8. swarm_test-0.1.0/pyproject.toml +102 -0
  9. swarm_test-0.1.0/swarm_test/__init__.py +43 -0
  10. swarm_test-0.1.0/swarm_test/attacks/__init__.py +1 -0
  11. swarm_test-0.1.0/swarm_test/attacks/base.py +38 -0
  12. swarm_test-0.1.0/swarm_test/attacks/blast_radius.py +201 -0
  13. swarm_test-0.1.0/swarm_test/attacks/cascade.py +120 -0
  14. swarm_test-0.1.0/swarm_test/attacks/collusion.py +247 -0
  15. swarm_test-0.1.0/swarm_test/attacks/context_leakage.py +168 -0
  16. swarm_test-0.1.0/swarm_test/attacks/intent_drift.py +247 -0
  17. swarm_test-0.1.0/swarm_test/cli.py +155 -0
  18. swarm_test-0.1.0/swarm_test/core/__init__.py +1 -0
  19. swarm_test-0.1.0/swarm_test/core/graph.py +219 -0
  20. swarm_test-0.1.0/swarm_test/core/interceptor.py +153 -0
  21. swarm_test-0.1.0/swarm_test/core/models.py +184 -0
  22. swarm_test-0.1.0/swarm_test/core/probe.py +209 -0
  23. swarm_test-0.1.0/swarm_test/integrations/__init__.py +1 -0
  24. swarm_test-0.1.0/swarm_test/integrations/base.py +106 -0
  25. swarm_test-0.1.0/swarm_test/integrations/crewai_adapter.py +144 -0
  26. swarm_test-0.1.0/swarm_test/reporters/__init__.py +1 -0
  27. swarm_test-0.1.0/swarm_test/reporters/console.py +151 -0
  28. swarm_test-0.1.0/swarm_test/reporters/html.py +337 -0
  29. swarm_test-0.1.0/tests/test_core.py +764 -0
@@ -0,0 +1,95 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test (Python ${{ matrix.python-version }})
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.10", "3.11", "3.12"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+ cache: pip
26
+
27
+ - name: Install dependencies
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -e ".[dev]"
31
+
32
+ - name: Run tests with coverage
33
+ run: |
34
+ pytest tests/ -v --cov=swarm_test --cov-report=term-missing --cov-report=xml --cov-fail-under=70
35
+
36
+ - name: Upload coverage to Codecov
37
+ uses: codecov/codecov-action@v4
38
+ if: matrix.python-version == '3.11'
39
+ with:
40
+ file: ./coverage.xml
41
+ fail_ci_if_error: false
42
+
43
+ lint:
44
+ name: Lint
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+
49
+ - name: Set up Python
50
+ uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.11"
53
+ cache: pip
54
+
55
+ - name: Install linting tools
56
+ run: |
57
+ pip install ruff black mypy
58
+
59
+ - name: Run ruff
60
+ run: ruff check swarm_test/
61
+
62
+ - name: Run black check
63
+ run: black --check swarm_test/
64
+
65
+ - name: Run mypy
66
+ run: mypy swarm_test/ --ignore-missing-imports
67
+
68
+ example:
69
+ name: Run example
70
+ runs-on: ubuntu-latest
71
+ steps:
72
+ - uses: actions/checkout@v4
73
+
74
+ - name: Set up Python
75
+ uses: actions/setup-python@v5
76
+ with:
77
+ python-version: "3.11"
78
+ cache: pip
79
+
80
+ - name: Install package
81
+ run: pip install -e ".[dev]"
82
+
83
+ - name: Run research_crew example
84
+ run: python examples/research_crew.py
85
+ timeout-minutes: 2
86
+
87
+ - name: Test CLI probe
88
+ run: swarm-test --help
89
+
90
+ - name: Test CLI scan
91
+ run: |
92
+ swarm-test scan \
93
+ --agents ResearchAgent --agents DataAgent --agents WriterAgent \
94
+ --edges "ResearchAgent:DataAgent" --edges "DataAgent:WriterAgent" \
95
+ --name "ci-test-swarm"
@@ -0,0 +1,41 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ *.egg
11
+
12
+ # Virtual environments
13
+ venv/
14
+ .venv/
15
+ env/
16
+
17
+ # Testing
18
+ .pytest_cache/
19
+ .coverage
20
+ htmlcov/
21
+
22
+ # Generated reports (keep examples intact)
23
+ /*.html
24
+
25
+ # Environment variables
26
+ .env
27
+ .env.*
28
+
29
+ # Claude Code
30
+ .claude/
31
+
32
+ # IDE
33
+ .vscode/
34
+ .idea/
35
+ *.swp
36
+ *.swo
37
+ *~
38
+
39
+ # OS
40
+ .DS_Store
41
+ Thumbs.db
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 swarm-test contributors
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.
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: swarm-test
3
+ Version: 0.1.0
4
+ Summary: The first reliability testing framework for multi-agent AI systems
5
+ Project-URL: Homepage, https://github.com/surajkumar811/swarm-test
6
+ Project-URL: Documentation, https://github.com/surajkumar811/swarm-test#readme
7
+ Project-URL: Repository, https://github.com/surajkumar811/swarm-test
8
+ Project-URL: Issues, https://github.com/surajkumar811/swarm-test/issues
9
+ Author: swarm-test contributors
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,autogen,chaos,crewai,langchain,multi-agent,reliability,testing
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: click>=8.1
24
+ Requires-Dist: colorlog>=6.7
25
+ Requires-Dist: jinja2>=3.1
26
+ Requires-Dist: networkx>=3.1
27
+ Requires-Dist: pydantic>=2.0
28
+ Requires-Dist: python-dotenv>=1.0
29
+ Requires-Dist: rich>=13.0
30
+ Provides-Extra: crewai
31
+ Requires-Dist: crewai>=0.28; extra == 'crewai'
32
+ Provides-Extra: dev
33
+ Requires-Dist: black>=23.0; extra == 'dev'
34
+ Requires-Dist: ipython; extra == 'dev'
35
+ Requires-Dist: mypy>=1.7; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
38
+ Requires-Dist: pytest>=7.4; extra == 'dev'
39
+ Requires-Dist: ruff>=0.1; extra == 'dev'
40
+ Requires-Dist: types-networkx; extra == 'dev'
41
+ Provides-Extra: langchain
42
+ Requires-Dist: langchain>=0.1; extra == 'langchain'
43
+ Requires-Dist: langgraph>=0.0.20; extra == 'langchain'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # swarm-test
47
+
48
+ **The first reliability testing framework for multi-agent AI systems.**
49
+
50
+ swarm-test builds a NetworkX interaction graph of your agent swarm and runs 5 automated chaos tests to surface cascade failures, context leakage, intent drift, collusion, and blast radius risks — all from a 3-line API.
51
+
52
+ ```python
53
+ from swarm_test import SwarmProbe
54
+
55
+ probe = SwarmProbe(crew)
56
+ report = probe.run_all()
57
+ report.print_summary()
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Features
63
+
64
+ | Test | What it checks |
65
+ |---|---|
66
+ | **Cascade Failure** | Which agents, if they fail, bring down the most of the swarm |
67
+ | **Context Leakage** | Sensitive data (credentials, PII) crossing agent boundaries |
68
+ | **Intent Drift** | Agents acting outside their role; prompt injection; goal hijacking |
69
+ | **Collusion Detection** | Dense cliques, echo chambers, orchestrator-bypass cycles |
70
+ | **Blast Radius** | Single points of failure, critical path, redundancy score |
71
+
72
+ ---
73
+
74
+ ## Installation
75
+
76
+ ```bash
77
+ pip install swarm-test
78
+ # or with framework extras:
79
+ pip install "swarm-test[crewai]"
80
+ pip install "swarm-test[langchain]"
81
+ ```
82
+
83
+ From source:
84
+
85
+ ```bash
86
+ git clone https://github.com/surajkumar811/swarm-test
87
+ cd swarm-test
88
+ pip install -e ".[dev]"
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Quick Start
94
+
95
+ ### With a CrewAI crew
96
+
97
+ ```python
98
+ from crewai import Crew, Agent, Task
99
+ from swarm_test import SwarmProbe
100
+
101
+ researcher = Agent(role="researcher", goal="...", backstory="...")
102
+ writer = Agent(role="writer", goal="...", backstory="...")
103
+ crew = Crew(agents=[researcher, writer], tasks=[...])
104
+
105
+ probe = SwarmProbe(crew, swarm_name="my-crew")
106
+ report = probe.run_all()
107
+ report.print_summary()
108
+ report.to_html("report.html") # D3 graph visualization
109
+ ```
110
+
111
+ ### Static graph (no live swarm)
112
+
113
+ ```python
114
+ from swarm_test import SwarmProbe, AgentNode, InteractionEvent, EventType
115
+
116
+ a = AgentNode(name="Fetcher", role="researcher")
117
+ b = AgentNode(name="Summarizer", role="writer")
118
+
119
+ probe = SwarmProbe(
120
+ swarm_name="my-swarm",
121
+ agents=[a, b],
122
+ events=[InteractionEvent(
123
+ source_agent_id=a.id,
124
+ target_agent_id=b.id,
125
+ event_type=EventType.TASK_DELEGATE,
126
+ )],
127
+ )
128
+ report = probe.run_all()
129
+ report.print_summary()
130
+ ```
131
+
132
+ ### CLI
133
+
134
+ ```bash
135
+ # Run against a Python script containing a `crew` variable
136
+ swarm-test probe my_crew.py --output report.html --fail-on-critical
137
+
138
+ # Static scan from the command line
139
+ swarm-test scan \
140
+ --agents Researcher --agents Analyst --agents Writer \
141
+ --edges "Researcher:Analyst" --edges "Analyst:Writer" \
142
+ --output report.html
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Architecture
148
+
149
+ ```
150
+ swarm_test/
151
+ ├── core/
152
+ │ ├── models.py # Pydantic models (AgentNode, Finding, SwarmReport, …)
153
+ │ ├── graph.py # NetworkX SwarmGraph
154
+ │ ├── interceptor.py # Monkey-patch agent methods, sensitive-data scanner
155
+ │ └── probe.py # SwarmProbe — main entry point
156
+ ├── attacks/
157
+ │ ├── cascade.py # Cascade failure simulation
158
+ │ ├── context_leakage.py # Sensitive-data boundary check
159
+ │ ├── intent_drift.py # Role violations + goal hijacking
160
+ │ ├── collusion.py # Clique/echo-chamber/cycle detection
161
+ │ └── blast_radius.py # Topological SPOF + redundancy analysis
162
+ ├── integrations/
163
+ │ ├── base.py # BaseAdapter
164
+ │ └── crewai_adapter.py # CrewAI Crew ingestion
165
+ ├── reporters/
166
+ │ ├── console.py # Rich terminal output
167
+ │ └── html.py # D3 force-directed graph report
168
+ └── cli.py # Click CLI
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Report Output
174
+
175
+ ### Terminal (Rich)
176
+
177
+ ```
178
+ ─────────────────── SWARM-TEST RELIABILITY REPORT ───────────────────
179
+
180
+ Summary
181
+ Swarm: research-crew-demo Framework: crewai
182
+ Agents: 4 Edges: 6
183
+ Risk Score: 45/100
184
+ Duration: 12ms
185
+
186
+ ╭─────────────────── Test Results ─────────────────────╮
187
+ │ Test Status Findings Critical High │
188
+ │ cascade_failure FAILED 2 1 1 │
189
+ │ context_leakage PASSED 0 0 0 │
190
+ │ intent_drift PASSED 0 0 0 │
191
+ │ collusion_detection PASSED 0 0 0 │
192
+ │ blast_radius FAILED 1 1 0 │
193
+ ╰───────────────────────────────────────────────────────╯
194
+ ```
195
+
196
+ ### HTML Report
197
+
198
+ Interactive D3.js force-directed graph showing agent nodes, interaction edges, and color-coded findings.
199
+
200
+ ---
201
+
202
+ ## Extending
203
+
204
+ ### Custom attack
205
+
206
+ ```python
207
+ from swarm_test.attacks.base import BaseAttack
208
+ from swarm_test.core.models import Finding, Severity, TestResult
209
+
210
+ class MyCustomAttack(BaseAttack):
211
+ name = "my_custom_attack"
212
+
213
+ def run(self, graph):
214
+ findings = []
215
+ # ... analyze graph.graph, graph.events ...
216
+ return TestResult(test_name=self.name, findings=findings)
217
+ ```
218
+
219
+ ### Custom adapter
220
+
221
+ ```python
222
+ from swarm_test.integrations.base import BaseAdapter
223
+
224
+ class MyFrameworkAdapter(BaseAdapter):
225
+ framework_name = "my-framework"
226
+
227
+ def _ingest_impl(self, swarm, graph):
228
+ for raw_agent in swarm.my_agents:
229
+ node = self._make_agent_node(raw_agent.name, raw_agent.role)
230
+ graph.add_agent(node)
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Development
236
+
237
+ ```bash
238
+ pip install -e ".[dev]"
239
+ pytest tests/ -v --cov=swarm_test
240
+ ruff check swarm_test/
241
+ black swarm_test/
242
+ ```
243
+
244
+ ---
245
+
246
+ ## License
247
+
248
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,203 @@
1
+ # swarm-test
2
+
3
+ **The first reliability testing framework for multi-agent AI systems.**
4
+
5
+ swarm-test builds a NetworkX interaction graph of your agent swarm and runs 5 automated chaos tests to surface cascade failures, context leakage, intent drift, collusion, and blast radius risks — all from a 3-line API.
6
+
7
+ ```python
8
+ from swarm_test import SwarmProbe
9
+
10
+ probe = SwarmProbe(crew)
11
+ report = probe.run_all()
12
+ report.print_summary()
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Features
18
+
19
+ | Test | What it checks |
20
+ |---|---|
21
+ | **Cascade Failure** | Which agents, if they fail, bring down the most of the swarm |
22
+ | **Context Leakage** | Sensitive data (credentials, PII) crossing agent boundaries |
23
+ | **Intent Drift** | Agents acting outside their role; prompt injection; goal hijacking |
24
+ | **Collusion Detection** | Dense cliques, echo chambers, orchestrator-bypass cycles |
25
+ | **Blast Radius** | Single points of failure, critical path, redundancy score |
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install swarm-test
33
+ # or with framework extras:
34
+ pip install "swarm-test[crewai]"
35
+ pip install "swarm-test[langchain]"
36
+ ```
37
+
38
+ From source:
39
+
40
+ ```bash
41
+ git clone https://github.com/surajkumar811/swarm-test
42
+ cd swarm-test
43
+ pip install -e ".[dev]"
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Quick Start
49
+
50
+ ### With a CrewAI crew
51
+
52
+ ```python
53
+ from crewai import Crew, Agent, Task
54
+ from swarm_test import SwarmProbe
55
+
56
+ researcher = Agent(role="researcher", goal="...", backstory="...")
57
+ writer = Agent(role="writer", goal="...", backstory="...")
58
+ crew = Crew(agents=[researcher, writer], tasks=[...])
59
+
60
+ probe = SwarmProbe(crew, swarm_name="my-crew")
61
+ report = probe.run_all()
62
+ report.print_summary()
63
+ report.to_html("report.html") # D3 graph visualization
64
+ ```
65
+
66
+ ### Static graph (no live swarm)
67
+
68
+ ```python
69
+ from swarm_test import SwarmProbe, AgentNode, InteractionEvent, EventType
70
+
71
+ a = AgentNode(name="Fetcher", role="researcher")
72
+ b = AgentNode(name="Summarizer", role="writer")
73
+
74
+ probe = SwarmProbe(
75
+ swarm_name="my-swarm",
76
+ agents=[a, b],
77
+ events=[InteractionEvent(
78
+ source_agent_id=a.id,
79
+ target_agent_id=b.id,
80
+ event_type=EventType.TASK_DELEGATE,
81
+ )],
82
+ )
83
+ report = probe.run_all()
84
+ report.print_summary()
85
+ ```
86
+
87
+ ### CLI
88
+
89
+ ```bash
90
+ # Run against a Python script containing a `crew` variable
91
+ swarm-test probe my_crew.py --output report.html --fail-on-critical
92
+
93
+ # Static scan from the command line
94
+ swarm-test scan \
95
+ --agents Researcher --agents Analyst --agents Writer \
96
+ --edges "Researcher:Analyst" --edges "Analyst:Writer" \
97
+ --output report.html
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Architecture
103
+
104
+ ```
105
+ swarm_test/
106
+ ├── core/
107
+ │ ├── models.py # Pydantic models (AgentNode, Finding, SwarmReport, …)
108
+ │ ├── graph.py # NetworkX SwarmGraph
109
+ │ ├── interceptor.py # Monkey-patch agent methods, sensitive-data scanner
110
+ │ └── probe.py # SwarmProbe — main entry point
111
+ ├── attacks/
112
+ │ ├── cascade.py # Cascade failure simulation
113
+ │ ├── context_leakage.py # Sensitive-data boundary check
114
+ │ ├── intent_drift.py # Role violations + goal hijacking
115
+ │ ├── collusion.py # Clique/echo-chamber/cycle detection
116
+ │ └── blast_radius.py # Topological SPOF + redundancy analysis
117
+ ├── integrations/
118
+ │ ├── base.py # BaseAdapter
119
+ │ └── crewai_adapter.py # CrewAI Crew ingestion
120
+ ├── reporters/
121
+ │ ├── console.py # Rich terminal output
122
+ │ └── html.py # D3 force-directed graph report
123
+ └── cli.py # Click CLI
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Report Output
129
+
130
+ ### Terminal (Rich)
131
+
132
+ ```
133
+ ─────────────────── SWARM-TEST RELIABILITY REPORT ───────────────────
134
+
135
+ Summary
136
+ Swarm: research-crew-demo Framework: crewai
137
+ Agents: 4 Edges: 6
138
+ Risk Score: 45/100
139
+ Duration: 12ms
140
+
141
+ ╭─────────────────── Test Results ─────────────────────╮
142
+ │ Test Status Findings Critical High │
143
+ │ cascade_failure FAILED 2 1 1 │
144
+ │ context_leakage PASSED 0 0 0 │
145
+ │ intent_drift PASSED 0 0 0 │
146
+ │ collusion_detection PASSED 0 0 0 │
147
+ │ blast_radius FAILED 1 1 0 │
148
+ ╰───────────────────────────────────────────────────────╯
149
+ ```
150
+
151
+ ### HTML Report
152
+
153
+ Interactive D3.js force-directed graph showing agent nodes, interaction edges, and color-coded findings.
154
+
155
+ ---
156
+
157
+ ## Extending
158
+
159
+ ### Custom attack
160
+
161
+ ```python
162
+ from swarm_test.attacks.base import BaseAttack
163
+ from swarm_test.core.models import Finding, Severity, TestResult
164
+
165
+ class MyCustomAttack(BaseAttack):
166
+ name = "my_custom_attack"
167
+
168
+ def run(self, graph):
169
+ findings = []
170
+ # ... analyze graph.graph, graph.events ...
171
+ return TestResult(test_name=self.name, findings=findings)
172
+ ```
173
+
174
+ ### Custom adapter
175
+
176
+ ```python
177
+ from swarm_test.integrations.base import BaseAdapter
178
+
179
+ class MyFrameworkAdapter(BaseAdapter):
180
+ framework_name = "my-framework"
181
+
182
+ def _ingest_impl(self, swarm, graph):
183
+ for raw_agent in swarm.my_agents:
184
+ node = self._make_agent_node(raw_agent.name, raw_agent.role)
185
+ graph.add_agent(node)
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Development
191
+
192
+ ```bash
193
+ pip install -e ".[dev]"
194
+ pytest tests/ -v --cov=swarm_test
195
+ ruff check swarm_test/
196
+ black swarm_test/
197
+ ```
198
+
199
+ ---
200
+
201
+ ## License
202
+
203
+ MIT — see [LICENSE](LICENSE).