Metadata-Version: 2.4
Name: toolsconnector
Version: 0.3.6
Summary: A foundational primitive for standardizing external API tool connections — for both traditional applications and AI agents.
Project-URL: Homepage, https://toolsconnector.github.io
Project-URL: Documentation, https://toolsconnector.github.io
Project-URL: Repository, https://github.com/sachinshelke/ToolsConnector
Project-URL: Issues, https://github.com/sachinshelke/ToolsConnector/issues
Project-URL: Changelog, https://github.com/sachinshelke/ToolsConnector/blob/main/CHANGELOG.md
Author-email: Sachin Shelke <sachin.worldnet@gmail.com>
Maintainer-email: Sachin Shelke <sachin.worldnet@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: agent,ai,api,async,connectors,http,httpx,integration,integrations,langchain,llm,mcp,openai,pydantic,rest,tool-use,tools
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: docstring-parser<1.0,>=0.15
Requires-Dist: eval-type-backport>=0.2.0; python_version < '3.10'
Requires-Dist: httpx<1.0,>=0.25.0
Requires-Dist: pydantic<3.0,>=2.0
Provides-Extra: acm
Provides-Extra: ai
Provides-Extra: airtable
Provides-Extra: alb
Provides-Extra: all
Requires-Dist: google-api-python-client>=2.0; extra == 'all'
Requires-Dist: google-auth-oauthlib>=1.0; extra == 'all'
Requires-Dist: google-auth>=2.0; extra == 'all'
Requires-Dist: mcp>=1.0; extra == 'all'
Requires-Dist: starlette>=0.27; extra == 'all'
Requires-Dist: uvicorn>=0.23; extra == 'all'
Provides-Extra: anthropic
Provides-Extra: asana
Provides-Extra: auth0
Provides-Extra: aws
Provides-Extra: aws-data
Provides-Extra: aws-deploy
Provides-Extra: aws-infra
Provides-Extra: aws-keystore
Requires-Dist: boto3>=1.28; extra == 'aws-keystore'
Provides-Extra: aws-monitor
Provides-Extra: calendly
Provides-Extra: cloudflare
Provides-Extra: cloudfront
Provides-Extra: cloudwatch
Provides-Extra: communication
Requires-Dist: google-api-python-client>=2.0; extra == 'communication'
Requires-Dist: google-auth-oauthlib>=1.0; extra == 'communication'
Requires-Dist: google-auth>=2.0; extra == 'communication'
Provides-Extra: confluence
Provides-Extra: crm
Provides-Extra: databases
Provides-Extra: datadog
Provides-Extra: dev
Requires-Dist: bandit>=1.7; extra == 'dev'
Requires-Dist: mypy>=1.5; extra == 'dev'
Requires-Dist: pip-audit>=2.6; extra == 'dev'
Requires-Dist: pre-commit>=3.5; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Requires-Dist: vcrpy>=6.0; extra == 'dev'
Provides-Extra: devops
Provides-Extra: discord
Provides-Extra: dockerhub
Provides-Extra: ec2
Provides-Extra: ecr
Provides-Extra: ecs
Provides-Extra: figma
Provides-Extra: firestore
Provides-Extra: freshdesk
Provides-Extra: gcalendar
Requires-Dist: google-api-python-client>=2.0; extra == 'gcalendar'
Requires-Dist: google-auth>=2.0; extra == 'gcalendar'
Provides-Extra: gcs-storage
Requires-Dist: google-cloud-storage>=2.0; extra == 'gcs-storage'
Provides-Extra: gdocs
Provides-Extra: gdrive
Requires-Dist: google-api-python-client>=2.0; extra == 'gdrive'
Requires-Dist: google-auth>=2.0; extra == 'gdrive'
Provides-Extra: github
Provides-Extra: gitlab
Provides-Extra: gmail
Requires-Dist: google-api-python-client>=2.0; extra == 'gmail'
Requires-Dist: google-auth-oauthlib>=1.0; extra == 'gmail'
Requires-Dist: google-auth>=2.0; extra == 'gmail'
Provides-Extra: google
Requires-Dist: google-api-python-client>=2.0; extra == 'google'
Requires-Dist: google-auth-oauthlib>=1.0; extra == 'google'
Requires-Dist: google-auth>=2.0; extra == 'google'
Provides-Extra: graphql
Requires-Dist: aiohttp>=3.8; extra == 'graphql'
Requires-Dist: gql>=3.4; extra == 'graphql'
Provides-Extra: grpc
Requires-Dist: grpcio>=1.50; extra == 'grpc'
Requires-Dist: protobuf>=4.0; extra == 'grpc'
Provides-Extra: gsheets
Provides-Extra: gtasks
Provides-Extra: hubspot
Provides-Extra: iam
Provides-Extra: intercom
Provides-Extra: jira
Provides-Extra: lambda-connector
Provides-Extra: linear
Provides-Extra: linkedin
Provides-Extra: mailchimp
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == 'mcp'
Provides-Extra: medium
Provides-Extra: microsoft
Provides-Extra: mixpanel
Provides-Extra: mongodb
Provides-Extra: notion
Provides-Extra: okta
Provides-Extra: openai
Provides-Extra: orchestrator
Requires-Dist: anthropic>=0.40.0; extra == 'orchestrator'
Requires-Dist: pyyaml>=6.0; extra == 'orchestrator'
Requires-Dist: rich>=13.0; extra == 'orchestrator'
Provides-Extra: outlook
Provides-Extra: pagerduty
Provides-Extra: pinecone
Provides-Extra: plaid
Provides-Extra: project-mgmt
Provides-Extra: publishers
Provides-Extra: rabbitmq
Provides-Extra: rds
Provides-Extra: redis
Provides-Extra: rest
Requires-Dist: starlette>=0.27; extra == 'rest'
Requires-Dist: uvicorn>=0.23; extra == 'rest'
Provides-Extra: route53
Provides-Extra: s3
Provides-Extra: s3-storage
Requires-Dist: boto3>=1.28; extra == 's3-storage'
Provides-Extra: salesforce
Provides-Extra: secrets-manager
Provides-Extra: segment
Provides-Extra: sendgrid
Provides-Extra: shopify
Provides-Extra: slack
Provides-Extra: soap
Requires-Dist: zeep>=4.2; extra == 'soap'
Provides-Extra: sqs
Provides-Extra: stripe
Provides-Extra: supabase
Provides-Extra: teams
Provides-Extra: telegram
Provides-Extra: trello
Provides-Extra: twilio
Provides-Extra: vault
Requires-Dist: hvac>=1.0; extra == 'vault'
Provides-Extra: vercel
Provides-Extra: webhook
Provides-Extra: websocket
Requires-Dist: websockets>=12.0; extra == 'websocket'
Provides-Extra: x
Provides-Extra: zendesk
Description-Content-Type: text/markdown

# ToolsConnector

**One interface, every tool. Connect 68+ APIs to your Python app or AI agent in minutes.**

[![PyPI version](https://img.shields.io/pypi/v/toolsconnector.svg?color=blue)](https://pypi.org/project/toolsconnector/)
[![Python 3.9+](https://img.shields.io/pypi/pyversions/toolsconnector.svg)](https://pypi.org/project/toolsconnector/)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![CI](https://github.com/sachinshelke/ToolsConnector/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/sachinshelke/ToolsConnector/actions/workflows/ci.yml)
[![CodeQL](https://github.com/sachinshelke/ToolsConnector/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/sachinshelke/ToolsConnector/security/code-scanning)
[![Pydantic V2](https://img.shields.io/badge/pydantic-v2-e92063.svg)](https://docs.pydantic.dev/)
[![PyPI downloads](https://img.shields.io/pypi/dm/toolsconnector.svg?color=brightgreen)](https://pypi.org/project/toolsconnector/)
[![Typed](https://img.shields.io/badge/typing-typed-blue.svg)](https://peps.python.org/pep-0561/)

---

## The Problem

Every SaaS API has its own SDK, its own auth dance, its own pagination scheme, and its own error format. If you're building an AI agent, you also need to generate JSON Schema for function calling -- differently for OpenAI, Anthropic, and Gemini. You end up writing glue code instead of product code.

ToolsConnector gives you a single, typed Python interface to **68 connectors and 1,370 actions**. It works identically whether you're building a Django app, an OpenAI agent, or an MCP server for Claude Desktop.

## Run the Documentation Site

To browse the full connector reference, playground, and guides locally:

```bash
python3 -m http.server 5001 --directory site
```

Then open **http://localhost:5001** in your browser.

---

## Install

```bash
pip install toolsconnector
```

Install only the connectors you need:

```bash
pip install "toolsconnector[gmail,slack,github]"
```

Or install a full category:

```bash
pip install "toolsconnector[communication,databases,mcp]"
```

## Quick Start

```python
from toolsconnector.serve import ToolKit

kit = ToolKit(
    ["gmail", "slack"],
    credentials={"gmail": "ya29.access-token", "slack": "xoxb-bot-token"},
)

# List unread emails
result = kit.execute("gmail_list_emails", {"query": "is:unread", "max_results": 5})

# Send a Slack message
kit.execute("slack_send_message", {"channel": "#general", "text": "Deployed v2.1"})
```

That's it. Same `ToolKit`, same `.execute()`, every connector.

## Try the Examples

Want to see real integrations end-to-end? **[`examples/`](examples/)** has 10 copy-pasteable scripts covering every major usage pattern. Pick one closest to what you want to build:

| Pattern | Script |
|---|---|
| 5-minute intro: ToolKit + execute | [`01_basic_usage.py`](examples/01_basic_usage.py) |
| One-line MCP server for Claude Desktop / Cursor | [`02_mcp_server.py`](examples/02_mcp_server.py) |
| OpenAI function-calling agent (full tool-use loop) | [`03_openai_function_calling.py`](examples/03_openai_function_calling.py) |
| Anthropic Claude tool-use agent | [`04_anthropic_tool_use.py`](examples/04_anthropic_tool_use.py) |
| Multi-connector agent with safety filtering | [`05_multi_connector.py`](examples/05_multi_connector.py) |
| `tc` CLI walkthrough | [`06_cli_usage.sh`](examples/06_cli_usage.sh) |
| Multi-tenant ToolKitFactory (per-user isolation) | [`07_multi_tenant.py`](examples/07_multi_tenant.py) |
| Expose connectors as REST API (Starlette + uvicorn) | [`08_rest_api.py`](examples/08_rest_api.py) |
| CI health checks, spec extraction, OpenAPI export | [`09_health_check.py`](examples/09_health_check.py) |
| Publish to LinkedIn end-to-end | [`10_linkedin_publish.py`](examples/10_linkedin_publish.py) |

See [`examples/README.md`](examples/README.md) for the full table with required env vars.

---

## Key Features

- **68 connectors, 1,370 actions** across 20 categories -- communication, social, databases, DevOps, CRM, AI/ML, AWS infrastructure, and more
- **Dual-use design** -- works for traditional Python apps (Django, Flask, FastAPI) and AI agents (function calling, tool use) with zero code changes
- **One-line MCP server** -- expose any combination of connectors to Claude Desktop, Cursor, or any MCP client
- **Schema generation** -- produces OpenAI, Anthropic, and Gemini function-calling schemas from the same source of truth
- **Type-safe everywhere** -- Pydantic V2 models for all inputs and outputs, with full JSON Schema generation
- **Async-first, sync-friendly** -- every action has both `await kit.aexecute()` and `kit.execute()` paths
- **Circuit breakers** -- per-connector failure isolation so one dead API doesn't take down your agent
- **Timeout budgets** -- per-action and per-request deadlines with automatic retry on transient failures
- **Dry-run mode** -- validate destructive actions without executing them
- **BYOK auth** -- bring your own API keys and tokens; no OAuth server required in the library
- **Minimal dependencies** -- core requires only `pydantic`, `httpx`, and `docstring-parser`

---

## Workflows

### 1. Direct Python Usage

Use connectors directly in any Python application.

```python
from toolsconnector.serve import ToolKit

kit = ToolKit(["github"], credentials={"github": "ghp_your_token"})

# List open issues
issues = kit.execute("github_list_issues", {
    "owner": "myorg",
    "repo": "myproject",
    "state": "open",
})
```

### 2. MCP Server (One Line)

Expose connectors to Claude Desktop, Cursor, Windsurf, or any MCP client.

```python
from toolsconnector.serve import ToolKit

kit = ToolKit(
    ["gmail", "gcalendar", "notion"],
    credentials={"gmail": "ya29.token", "gcalendar": "ya29.token", "notion": "ntn_key"},
)
kit.serve_mcp()  # stdio transport, ready for Claude Desktop
```

Or from the command line:

```bash
# Stdio (one client per process — Claude Desktop launches it as subprocess)
tc serve mcp gmail gcalendar notion --transport stdio

# Long-lived HTTP daemon — multiple agents share one process
tc serve mcp gmail slack github --transport streamable-http --port 9000
```

The HTTP transport (`streamable-http`) accepts many concurrent client
sessions on the same port — one daemon serves N agents. Per-tool
circuit breakers and timeout budgets apply fairly across all clients.
There's no built-in auth on the HTTP transport; put it behind a reverse
proxy before exposing it publicly.

### 3. OpenAI Function Calling

Generate tool schemas and execute tool calls from OpenAI responses.

```python
from openai import OpenAI
from toolsconnector.serve import ToolKit

client = OpenAI()
kit = ToolKit(["gmail", "slack"], credentials={...})

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Summarize my unread emails"}],
    tools=kit.to_openai_tools(),
)

# Execute the tool call the model chose
tool_call = response.choices[0].message.tool_calls[0]
result = kit.execute(tool_call.function.name, tool_call.function.arguments)
```

### 4. Anthropic Tool Use

Works the same way with Claude's tool use API.

```python
import anthropic
from toolsconnector.serve import ToolKit

client = anthropic.Anthropic()
kit = ToolKit(["jira", "slack"], credentials={...})

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Create a bug ticket for the login issue"}],
    tools=kit.to_anthropic_tools(),
)

# Execute the tool call
for block in response.content:
    if block.type == "tool_use":
        result = kit.execute(block.name, block.input)
```

### 5. Google Gemini

Generate Gemini-compatible function declarations.

```python
from toolsconnector.serve import ToolKit

kit = ToolKit(["gmail"], credentials={...})
declarations = kit.to_gemini_tools()
# Pass to google.generativeai as function_declarations
```

### 6. CLI Usage

Manage connectors and execute actions from the terminal.

```bash
# List all available connectors
tc list

# List actions for a specific connector
tc gmail actions

# Execute an action
tc gmail list_emails --query "is:unread" --max_results 5

# Export the connector spec
tc gmail spec --format json
```

### 7. REST API

Serve connectors as HTTP endpoints with Starlette/ASGI.

```python
from toolsconnector.serve import ToolKit

kit = ToolKit(["stripe", "hubspot"], credentials={...})
app = kit.create_rest_app(prefix="/api/v1")

# Run with uvicorn: uvicorn myapp:app --port 8000
# POST /api/v1/stripe/create_charge {"amount": 5000, "currency": "usd"}
```

---

## Error Handling

Every connector raises typed exceptions from `toolsconnector.errors` so
your code can branch on the failure mode instead of parsing HTTP status
codes:

```python
from toolsconnector.errors import (
    InvalidCredentialsError, TokenExpiredError, PermissionDeniedError,
    NotFoundError, RateLimitError, ValidationError, ServerError,
)

try:
    await kit.aexecute("gmail_send_email", {...})
except TokenExpiredError:
    await refresh_oauth_token()
    # ...retry...
except RateLimitError as e:
    await asyncio.sleep(e.retry_after_seconds)  # parsed from Retry-After header
    # ...retry...
except InvalidCredentialsError:
    prompt_user_to_reauthenticate()
except NotFoundError:
    return None  # 404 — caller decides what "missing" means
except PermissionDeniedError:
    request_additional_oauth_scopes()
except ValidationError:
    # 400 / 422 — your arguments were rejected by the upstream API
    raise
except ServerError:
    # 5xx — upstream is having a bad day; retry-eligible
    pass
```

All typed errors carry `connector`, `action`, `upstream_status`, and a
truncated `details["body_preview"]` — useful for logs and observability.
The full taxonomy lives in `src/toolsconnector/errors/` if you want to
match more granular cases (e.g. `ConflictError` for 409).

---

## Supported Connectors

68 connectors, 1,370 actions across 20 categories.

### Communication (7)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Gmail | `gmail` | 38 |
| Slack | `slack` | 51 |
| Discord | `discord` | 25 |
| Microsoft Outlook | `outlook` | 23 |
| Microsoft Teams | `teams` | 17 |
| Twilio | `twilio` | 20 |
| Telegram | `telegram` | 26 |

### Project Management (4)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Jira | `jira` | 28 |
| Asana | `asana` | 38 |
| Linear | `linear` | 19 |
| Trello | `trello` | 25 |

### CRM & Support (5)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| HubSpot | `hubspot` | 19 |
| Salesforce | `salesforce` | 21 |
| Zendesk | `zendesk` | 16 |
| Freshdesk | `freshdesk` | 23 |
| Intercom | `intercom` | 16 |

### Code Platforms (2)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| GitHub | `github` | 37 |
| GitLab | `gitlab` | 21 |

### Knowledge (2)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Notion | `notion` | 20 |
| Confluence | `confluence` | 25 |

### Storage (2)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Google Drive | `gdrive` | 22 |
| AWS S3 | `s3` | 20 |

### Database (5)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Airtable | `airtable` | 26 |
| Firebase Firestore | `firestore` | 17 |
| MongoDB Atlas | `mongodb` | 16 |
| Redis (Upstash) | `redis` | 18 |
| Supabase | `supabase` | 16 |

### DevOps (5)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Cloudflare | `cloudflare` | 23 |
| Datadog | `datadog` | 22 |
| Docker Hub | `dockerhub` | 14 |
| PagerDuty | `pagerduty` | 16 |
| Vercel | `vercel` | 16 |

### Finance (2)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Stripe | `stripe` | 40 |
| Plaid | `plaid` | 17 |

### Marketing (2)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Mailchimp | `mailchimp` | 23 |
| SendGrid | `sendgrid` | 20 |

### AI / ML (3)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| OpenAI | `openai` | 26 |
| Anthropic | `anthropic` | 14 |
| Pinecone | `pinecone` | 15 |

### Analytics (2)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Mixpanel | `mixpanel` | 14 |
| Segment | `segment` | 14 |

### Message Queue (2)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| AWS SQS | `sqs` | 16 |
| RabbitMQ | `rabbitmq` | 21 |

### Security (2)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Okta | `okta` | 21 |
| Auth0 | `auth0` | 27 |

### Productivity (6)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Google Calendar | `gcalendar` | 20 |
| Google Docs | `gdocs` | 5 |
| Google Sheets | `gsheets` | 16 |
| Google Tasks | `gtasks` | 13 |
| Calendly | `calendly` | 20 |
| Figma | `figma` | 22 |

### E-Commerce (1)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Shopify | `shopify` | 27 |

### Custom (1)

| Connector | Install Extra | Actions |
|-----------|---------------|---------|
| Webhook | `webhook` | 12 |

---

## Architecture

ToolsConnector is structured as four layers, each with a single responsibility:

```
+------------------------------------------------------------------+
|  Serve Layer       ToolKit, MCP, REST, CLI, Schema Generation    |
+------------------------------------------------------------------+
|  Runtime Engine    BaseConnector, @action, Middleware, Auth       |
+------------------------------------------------------------------+
|  Connectors        Gmail, Slack, GitHub, Stripe, ... (68)        |
+------------------------------------------------------------------+
|  Spec Types        Pydantic V2 models, JSON Schema, Contracts    |
+------------------------------------------------------------------+
```

**Spec** -- Pure Pydantic V2 models defining the language-agnostic connector contract. No implementation logic. These drive schema generation, MCP serving, documentation, and code generation.

**Runtime** -- The execution engine. `BaseConnector` is the abstract base class. The `@action` decorator parses type hints and docstrings to generate JSON Schema automatically. Middleware handles retry, rate limiting, auth refresh, and structured logging.

**Connectors** -- 68 implementations, each following the same pattern: subclass `BaseConnector`, set metadata, implement `@action` methods. Most use raw `httpx` for direct HTTP calls. Google and AWS connectors use official SDKs where protocol complexity justifies it.

**Serve** -- The `ToolKit` ties everything together. Configure once with a list of connectors and credentials, then serve as MCP, generate OpenAI/Anthropic/Gemini schemas, expose as REST, or call directly from Python.

### Adding a Connector

Every connector follows the same structure:

```python
from toolsconnector.runtime import BaseConnector, action
from toolsconnector.spec.connector import ConnectorCategory, ProtocolType

class MyService(BaseConnector):
    name = "myservice"
    display_name = "My Service"
    category = ConnectorCategory.COMMUNICATION
    protocol = ProtocolType.REST
    base_url = "https://api.myservice.com/v1"

    @action(description="List all items", idempotent=True)
    async def list_items(self, limit: int = 20) -> list[dict]:
        """List items from the service.

        Args:
            limit: Maximum number of items to return.
        """
        resp = await self._request("GET", "/items", params={"limit": limit})
        return resp.json()["items"]
```

The `@action` decorator handles everything: it parses the type hints and docstring to generate JSON Schema, creates a sync wrapper, and registers the method for discovery by `ToolKit`.

---

## Contributing

1. Fork the repository
2. Create a connector under `src/toolsconnector/connectors/yourservice/`
3. Subclass `BaseConnector` and implement `@action` methods
4. Add types in a `types.py` module using Pydantic V2 models
5. Add the install extra to `pyproject.toml`
6. Write tests under `tests/connectors/yourservice/`
7. Submit a pull request

See the existing connectors (e.g., `src/toolsconnector/connectors/slack/`) for reference implementations.

## Requirements

- Python 3.9+
- Core dependencies: `pydantic>=2.0`, `httpx>=0.25`, `docstring-parser>=0.15`
- Connector-specific dependencies installed via extras (e.g., `gmail` extra installs `google-api-python-client`)

## License

Apache License 2.0. See [LICENSE](LICENSE) for details.
