rymi 1.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.
rymi/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ from typing import Optional
2
+ from .client import RymiClient, RymiError
3
+ from .resources.agents import AgentsResource
4
+ from .resources.calls import CallsResource
5
+ from .resources.numbers import NumbersResource
6
+ from .resources.telephony import TelephonyResource
7
+ from .resources.keys import KeysResource
8
+ from .resources.billing import BillingResource
9
+ from .resources.templates import TemplatesResource
10
+ from .resources.webhooks import WebhooksUtility
11
+
12
+ class Rymi:
13
+ """The official Python SDK for the Rymi Voice API."""
14
+
15
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
16
+ self._client = RymiClient(api_key=api_key, base_url=base_url)
17
+
18
+ self.agents = AgentsResource(self._client)
19
+ self.calls = CallsResource(self._client)
20
+ self.numbers = NumbersResource(self._client)
21
+ self.telephony = TelephonyResource(self._client)
22
+ self.keys = KeysResource(self._client)
23
+ self.billing = BillingResource(self._client)
24
+ self.templates = TemplatesResource(self._client)
25
+ self.webhooks = WebhooksUtility()
26
+
27
+ __all__ = ["Rymi", "RymiError"]
rymi/client.py ADDED
@@ -0,0 +1,76 @@
1
+ import os
2
+ import requests
3
+ from typing import Any, Dict, Optional
4
+ from urllib.parse import urlparse
5
+
6
+ class RymiError(Exception):
7
+ """Custom exception raised for Rymi API Errors."""
8
+ def __init__(self, message: str, status: Optional[int] = None, code: Optional[str] = None):
9
+ super().__init__(message)
10
+ self.status = status
11
+ self.code = code
12
+
13
+ class RymiClient:
14
+ """Core HTTP Client for the Rymi API."""
15
+
16
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
17
+ self.api_key = api_key or os.environ.get("RYMI_API_KEY")
18
+ if not self.api_key:
19
+ raise ValueError(
20
+ "The Rymi API Key must be set either by passing `api_key` to the client "
21
+ "or setting the `RYMI_API_KEY` environment variable."
22
+ )
23
+
24
+ self.base_url = (base_url or "https://api.rymi.live/v1").rstrip("/")
25
+
26
+ self.session = requests.Session()
27
+ self.session.headers.update({
28
+ "Authorization": f"Bearer {self.api_key}",
29
+ "Accept": "application/json",
30
+ "User-Agent": "rymi-python/1.0.0"
31
+ })
32
+
33
+ def request(self, method: str, path: str, json: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Any:
34
+ url = f"{self.base_url}{path if path.startswith('/') else '/' + path}"
35
+
36
+ try:
37
+ response = self.session.request(method, url, json=json, params=params)
38
+
39
+ # Attempt to parse JSON regardless of status code to check for error details
40
+ data = None
41
+ if "application/json" in response.headers.get("Content-Type", ""):
42
+ try:
43
+ data = response.json()
44
+ except ValueError:
45
+ data = response.text
46
+ else:
47
+ data = response.text
48
+
49
+ if not response.ok:
50
+ message = response.reason
51
+ code = None
52
+
53
+ if isinstance(data, dict):
54
+ message = data.get("error", message)
55
+ code = data.get("code")
56
+ elif isinstance(data, str) and data:
57
+ message = data
58
+
59
+ raise RymiError(message, status=response.status_code, code=code)
60
+
61
+ return data
62
+
63
+ except requests.exceptions.RequestException as e:
64
+ raise RymiError(f"Network Error: {str(e)}")
65
+
66
+ def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
67
+ return self.request("GET", path, params=params)
68
+
69
+ def post(self, path: str, json: Optional[Dict[str, Any]] = None) -> Any:
70
+ return self.request("POST", path, json=json)
71
+
72
+ def put(self, path: str, json: Optional[Dict[str, Any]] = None) -> Any:
73
+ return self.request("PUT", path, json=json)
74
+
75
+ def delete(self, path: str) -> Any:
76
+ return self.request("DELETE", path)
@@ -0,0 +1,159 @@
1
+ from typing import Any, Dict, List, Optional
2
+ from rymi.client import RymiClient
3
+
4
+ class AgentsResource:
5
+ """Manage AI Agents."""
6
+
7
+ def __init__(self, client: RymiClient):
8
+ self.client = client
9
+
10
+ def list(self, **params: Any) -> Dict[str, Any]:
11
+ """Retrieve a list of all your AI Agents."""
12
+ return self.client.get("/agents", params=params or None)
13
+
14
+ def retrieve(self, agent_id: str) -> Dict[str, Any]:
15
+ """Retrieve a single AI Agent by its unique ID."""
16
+ return self.client.get(f"/agents/{agent_id}")
17
+
18
+ def create(self, name: str, system_prompt: Optional[str] = None, voice: Optional[str] = None, language: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
19
+ """Create a new AI Agent."""
20
+ prompt = kwargs.pop("prompt", None)
21
+ payload = {"name": name}
22
+ if system_prompt or prompt:
23
+ payload["system_prompt"] = system_prompt or prompt
24
+ if voice:
25
+ payload["voice"] = voice
26
+ if language:
27
+ payload["language"] = language
28
+ payload.update(kwargs)
29
+
30
+ return self.client.post("/agents", json=payload)
31
+
32
+ def update(self, agent_id: str, name: Optional[str] = None, system_prompt: Optional[str] = None, voice: Optional[str] = None, language: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
33
+ """Update an existing AI Agent."""
34
+ prompt = kwargs.pop("prompt", None)
35
+ payload = {}
36
+ if name: payload["name"] = name
37
+ if system_prompt or prompt: payload["system_prompt"] = system_prompt or prompt
38
+ if voice: payload["voice"] = voice
39
+ if language: payload["language"] = language
40
+ payload.update(kwargs)
41
+
42
+ return self.client.put(f"/agents/{agent_id}", json=payload)
43
+
44
+ def delete(self, agent_id: str) -> Dict[str, Any]:
45
+ """Delete an AI Agent permanently."""
46
+ return self.client.delete(f"/agents/{agent_id}")
47
+
48
+ def llm_options(self) -> Dict[str, Any]:
49
+ """Retrieve model and voice catalog options."""
50
+ return self.client.get("/agents/llm-options")
51
+
52
+ def generate(self, prompt: str, options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
53
+ """Generate a structured agent draft from a plain-English brief."""
54
+ payload: Dict[str, Any] = {"prompt": prompt}
55
+ if options:
56
+ payload["options"] = options
57
+ return self.client.post("/agents/generate", json=payload)
58
+
59
+ def list_calls(self, agent_id: str, limit: int = 50, offset: int = 0, status: Optional[str] = None) -> Dict[str, Any]:
60
+ """List calls made with a specific agent."""
61
+ params: Dict[str, Any] = {"limit": limit, "offset": offset}
62
+ if status:
63
+ params["status"] = status
64
+ return self.client.get(f"/agents/{agent_id}/calls", params=params)
65
+
66
+ def clone(self, agent_id: str) -> Dict[str, Any]:
67
+ """Duplicate an existing agent. The copy gets ' (Copy)' appended to its name."""
68
+ return self.client.post(f"/agents/{agent_id}/clone", json={})
69
+
70
+ def validate_publish(self, agent_id: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
71
+ """Check whether an agent is ready to go live without persisting changes."""
72
+ payload: Dict[str, Any] = {}
73
+ if agent_id:
74
+ payload["agent_id"] = agent_id
75
+ payload.update(kwargs)
76
+ return self.client.post("/agents/validate-publish", json=payload)
77
+
78
+ def apply_changes(self, current_config: Dict[str, Any], changes: list, mode: str = "edit", lenient: bool = False) -> Dict[str, Any]:
79
+ """Validate and resolve a flat key/value change-set. Does NOT persist — follow up with update()."""
80
+ return self.client.post("/agents/apply-changes", json={
81
+ "currentConfig": current_config,
82
+ "changes": changes,
83
+ "mode": mode,
84
+ "lenient": lenient,
85
+ })
86
+
87
+ def enrich_company(self, company_name: str, website_url: str) -> Dict[str, Any]:
88
+ """Auto-generate a company description from a website URL using AI + Google Search."""
89
+ return self.client.post("/agents/enrich-company", json={
90
+ "companyName": company_name,
91
+ "websiteUrl": website_url,
92
+ })
93
+
94
+ def preview_stack(
95
+ self,
96
+ supported_languages: List[str],
97
+ agent_role: Optional[str] = None,
98
+ language: Optional[str] = None,
99
+ current_provider_config: Optional[Dict[str, Any]] = None,
100
+ ) -> Dict[str, Any]:
101
+ """Resolve the per-language model stack (STT/LLM/TTS), blockers, warnings, and
102
+ required role upgrades without saving. The concierge (realtime) role is not
103
+ supported by this endpoint."""
104
+ payload: Dict[str, Any] = {"supported_languages": supported_languages}
105
+ if agent_role:
106
+ payload["agent_role"] = agent_role
107
+ if language is not None:
108
+ payload["language"] = language
109
+ if current_provider_config is not None:
110
+ payload["current_provider_config"] = current_provider_config
111
+ return self.client.post("/agents/stack-preview", json=payload)
112
+
113
+ def list_knowledge_sources(self, agent_id: str) -> Dict[str, Any]:
114
+ """List the knowledge sources (RAG context) attached to an agent."""
115
+ return self.client.get(f"/agents/{agent_id}/knowledge-sources")
116
+
117
+ def add_knowledge_source(
118
+ self,
119
+ agent_id: str,
120
+ kind: str,
121
+ title: str,
122
+ text: Optional[str] = None,
123
+ url: Optional[str] = None,
124
+ ) -> Dict[str, Any]:
125
+ """Add a knowledge source from raw text (kind='text') or a URL (kind='url')."""
126
+ payload: Dict[str, Any] = {"kind": kind, "title": title}
127
+ if text is not None:
128
+ payload["text"] = text
129
+ if url is not None:
130
+ payload["url"] = url
131
+ return self.client.post(f"/agents/{agent_id}/knowledge-sources", json=payload)
132
+
133
+ def delete_knowledge_source(self, agent_id: str, source_id: str) -> Dict[str, Any]:
134
+ """Delete a knowledge source from an agent."""
135
+ return self.client.delete(f"/agents/{agent_id}/knowledge-sources/{source_id}")
136
+
137
+ def list_changes(self, agent_id: str, since: Optional[str] = None) -> Dict[str, Any]:
138
+ """List recorded configuration changes for an agent (optionally since an ISO timestamp)."""
139
+ params = {"since": since} if since else None
140
+ return self.client.get(f"/agents/{agent_id}/changes", params=params)
141
+
142
+ def undo_change(self, agent_id: str, change_id: str) -> Dict[str, Any]:
143
+ """Undo a single recorded configuration change, reverting that field to its previous value."""
144
+ return self.client.post(f"/agents/{agent_id}/changes/{change_id}/undo", json={})
145
+
146
+ def run_evals(self, agent_id: str, mode: Optional[str] = None) -> Dict[str, Any]:
147
+ """Run the evaluation suite for an agent. mode='synthetic' (default) or 'live'."""
148
+ path = f"/agents/{agent_id}/evals/run"
149
+ if mode:
150
+ path += f"?mode={mode}"
151
+ return self.client.post(path, json={})
152
+
153
+ def list_eval_runs(self, agent_id: str) -> Dict[str, Any]:
154
+ """List previous evaluation runs for an agent."""
155
+ return self.client.get(f"/agents/{agent_id}/evals/runs")
156
+
157
+ def get_eval_run(self, agent_id: str, run_id: str) -> Dict[str, Any]:
158
+ """Retrieve a single evaluation run, including per-scenario scores."""
159
+ return self.client.get(f"/agents/{agent_id}/evals/runs/{run_id}")
@@ -0,0 +1,13 @@
1
+ from typing import Any, Dict
2
+ from rymi.client import RymiClient
3
+
4
+ class BillingResource:
5
+ """Account usage. Voice balance is reported in MINUTES, not dollars."""
6
+
7
+ def __init__(self, client: RymiClient):
8
+ self.client = client
9
+
10
+ def usage_summary(self) -> Dict[str, Any]:
11
+ """Lane-aware usage summary: remaining voice-runtime minutes, Studio AI unit
12
+ usage, and post-call intelligence usage."""
13
+ return self.client.get("/billing/usage-summary")
@@ -0,0 +1,74 @@
1
+ from typing import Any, Dict, List, Optional
2
+ from rymi.client import RymiClient
3
+
4
+ class CallsResource:
5
+ """Manage Outbound Calls."""
6
+
7
+ def __init__(self, client: RymiClient):
8
+ self.client = client
9
+
10
+ def list(self, **params: Any) -> Dict[str, Any]:
11
+ """Retrieve a list of all call records."""
12
+ return self.client.get("/calls", params=params or None)
13
+
14
+ def active(self, **params: Any) -> Dict[str, Any]:
15
+ """Retrieve active in-progress calls."""
16
+ return self.client.get("/calls/active", params=params or None)
17
+
18
+ def retrieve(self, call_id: str) -> Dict[str, Any]:
19
+ """Retrieve a single call record and transcript."""
20
+ return self.client.get(f"/calls/{call_id}")
21
+
22
+ def create(
23
+ self,
24
+ agent_id: str,
25
+ participants: Optional[List[Dict[str, Any]]] = None,
26
+ metadata: Optional[Dict[str, Any]] = None,
27
+ variables: Optional[Dict[str, Any]] = None,
28
+ post_call: Optional[Dict[str, Any]] = None,
29
+ to: Optional[str] = None,
30
+ from_number: Optional[str] = None
31
+ ) -> Dict[str, Any]:
32
+ """Create a WebRTC/PSTN call using the participant-first API."""
33
+ if participants is None and to:
34
+ participants = [{
35
+ "transport": "pstn",
36
+ "identity": to,
37
+ "from_number": from_number
38
+ }]
39
+
40
+ payload = {
41
+ "agent_id": agent_id,
42
+ "participants": participants or []
43
+ }
44
+ if metadata:
45
+ payload["metadata"] = metadata
46
+ if variables:
47
+ payload["variables"] = variables
48
+ if post_call:
49
+ payload["post_call"] = post_call
50
+
51
+ return self.client.post("/calls", json=payload)
52
+
53
+ def batch(self, agent_id: str, to: List[str], from_number: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
54
+ """Queue a batch of outbound PSTN recipients."""
55
+ payload = {"agent_id": agent_id, "to": to}
56
+ if from_number:
57
+ payload["from_number"] = from_number
58
+ payload.update(kwargs)
59
+ return self.client.post("/calls/batch", json=payload)
60
+
61
+ def summary(self, call_id: str) -> Dict[str, Any]:
62
+ return self.client.get(f"/calls/{call_id}/summary")
63
+
64
+ def transcript(self, call_id: str) -> Dict[str, Any]:
65
+ return self.client.get(f"/calls/{call_id}/transcript")
66
+
67
+ def recording(self, call_id: str) -> Dict[str, Any]:
68
+ return self.client.get(f"/calls/{call_id}/recording")
69
+
70
+ def reprocess(self, call_id: str) -> Dict[str, Any]:
71
+ return self.client.post(f"/calls/{call_id}/reprocess")
72
+
73
+ def queue_stats(self) -> Dict[str, Any]:
74
+ return self.client.get("/calls/queue/stats")
rymi/resources/keys.py ADDED
@@ -0,0 +1,16 @@
1
+ from typing import Any, Dict
2
+ from rymi.client import RymiClient
3
+
4
+ class KeysResource:
5
+ """Read-only access to publishable (browser-safe) keys.
6
+
7
+ Creating or revoking keys mints/destroys credentials and changes standing
8
+ config, so those remain dashboard actions and are not exposed here.
9
+ """
10
+
11
+ def __init__(self, client: RymiClient):
12
+ self.client = client
13
+
14
+ def list_publishable(self) -> Dict[str, Any]:
15
+ """List publishable keys and their agent/channel scoping. Returns key prefixes only, never full secrets."""
16
+ return self.client.get("/keys/publishable")
@@ -0,0 +1,24 @@
1
+ from typing import Any, Dict, Optional
2
+ from rymi.client import RymiClient
3
+
4
+ class NumbersResource:
5
+ """Manage already-owned BYOC phone numbers registered with Rymi."""
6
+
7
+ def __init__(self, client: RymiClient):
8
+ self.client = client
9
+
10
+ def list(self, **params: Any) -> Dict[str, Any]:
11
+ """Retrieve a list of all assigned phone numbers."""
12
+ return self.client.get("/numbers", params=params or None)
13
+
14
+ def register(self, number: str, agent_id: Optional[str] = None) -> Dict[str, Any]:
15
+ payload: Dict[str, Any] = {"number": number}
16
+ if agent_id is not None:
17
+ payload["agent_id"] = agent_id
18
+ return self.client.post("/numbers", json=payload)
19
+
20
+ def attach(self, number: str, agent_id: str) -> Dict[str, Any]:
21
+ return self.client.post(f"/numbers/{number}/attach", json={"agent_id": agent_id})
22
+
23
+ def remove(self, number: str) -> Dict[str, Any]:
24
+ return self.client.delete(f"/numbers/{number}")
@@ -0,0 +1,20 @@
1
+ from typing import Any, Dict
2
+ from rymi.client import RymiClient
3
+
4
+ class TelephonyResource:
5
+ """Inspect the connected telephony carrier.
6
+
7
+ Connecting/disconnecting a carrier requires carrier credentials and changes a
8
+ standing integration, so it is a dashboard action and not exposed here.
9
+ """
10
+
11
+ def __init__(self, client: RymiClient):
12
+ self.client = client
13
+
14
+ def status(self) -> Dict[str, Any]:
15
+ """Report whether a carrier is connected, and which provider/account."""
16
+ return self.client.get("/telephony/status")
17
+
18
+ def numbers(self) -> Dict[str, Any]:
19
+ """List numbers available on the connected telephony carrier account."""
20
+ return self.client.get("/telephony/numbers")
@@ -0,0 +1,12 @@
1
+ from typing import Any, Dict
2
+ from rymi.client import RymiClient
3
+
4
+ class TemplatesResource:
5
+ """Published agent templates."""
6
+
7
+ def __init__(self, client: RymiClient):
8
+ self.client = client
9
+
10
+ def list(self) -> Dict[str, Any]:
11
+ """List published agent templates. Use a template's `defaults` as the starting config for agents.create()."""
12
+ return self.client.get("/agent-templates")
@@ -0,0 +1,46 @@
1
+ import hmac
2
+ import hashlib
3
+ import time
4
+
5
+ class WebhooksUtility:
6
+ """Secure Utility for Webhook Signature Verification."""
7
+
8
+ @staticmethod
9
+ def verify_signature(
10
+ payload: str,
11
+ signature_header: str,
12
+ timestamp_header: str,
13
+ webhook_secret: str,
14
+ tolerance_millis: int = 300000 # 5 minutes
15
+ ) -> bool:
16
+ """
17
+ Verifies that a webhook received by your backend was genuinely dispatched by Rymi.
18
+
19
+ Rymi endpoints secure webhooks by computing an HMAC-SHA256 signature
20
+ of the raw JSON body concatenated with the timestamp using your Webhook Secret.
21
+ """
22
+ # 1. Check for Replay Attacks
23
+ try:
24
+ timestamp = int(timestamp_header)
25
+ except ValueError:
26
+ raise ValueError("Invalid Rymi-Timestamp header format.")
27
+
28
+ current_time = int(time.time() * 1000)
29
+ if (current_time - timestamp) > tolerance_millis:
30
+ raise ValueError("Webhook timestamp is too old. Possible replay attack.")
31
+
32
+ # 2. Prepare the verification string
33
+ signature_payload = f"{timestamp}.{payload}"
34
+
35
+ # 3. Compute expected HMAC
36
+ expected_signature = hmac.new(
37
+ webhook_secret.encode('utf-8'),
38
+ signature_payload.encode('utf-8'),
39
+ hashlib.sha256
40
+ ).hexdigest()
41
+
42
+ # 4. Secure string comparison
43
+ if not hmac.compare_digest(signature_header, expected_signature):
44
+ raise ValueError("Invalid Webhook Signature. The secret might be incorrect.")
45
+
46
+ return True
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: rymi
3
+ Version: 1.1.0
4
+ Summary: The official Python REST API wrapper for Rymi.
5
+ Author-email: Rymi AI <engineering@rymi.live>
6
+ Project-URL: Homepage, https://rymi.live
7
+ Project-URL: Documentation, https://rymi.live/docs
8
+ Project-URL: Source, https://github.com/rymi-ai/rymi
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: requests>=2.25.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
24
+ Requires-Dist: responses>=0.23.0; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # Rymi Python SDK
28
+
29
+ The official Python wrapper for the Rymi Voice AI REST API. Manage agents (incl. multi-language and model-stack config), phone numbers, calls, knowledge sources, evaluations, usage, and templates, plus webhook verification.
30
+
31
+ Resources: `agents` · `calls` · `numbers` · `telephony` · `keys` · `billing` · `templates` · `webhooks`.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install rymi
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```python
42
+ import os
43
+ from rymi import Rymi
44
+
45
+ # Automatically picks up RYMI_API_KEY from environment
46
+ client = Rymi()
47
+
48
+ # Or pass explicitly
49
+ # client = Rymi(api_key="rymi_live_...")
50
+
51
+ agents = client.agents.list()
52
+ for agent in agents["agents"]:
53
+ print(agent['name'])
54
+ ```
@@ -0,0 +1,15 @@
1
+ rymi/__init__.py,sha256=NQqdhCywP9BN0VMeZV-31R77wrv5-jo-Rhl7218mImk,1115
2
+ rymi/client.py,sha256=r-E6-R5DPkq9sNCMgu0Ikqq1cvKHZP3MWkbpoE-d_wc,2983
3
+ rymi/resources/agents.py,sha256=2IZ_lBvTRbP9RI88yQUgn676xWv6swFAPxF_rz_mixM,7501
4
+ rymi/resources/billing.py,sha256=mgb_BbZATx-jtCb9geHdwF3mMnDJG5g9R51u6Ntoy10,480
5
+ rymi/resources/calls.py,sha256=Rs1ft8f7-a3rOV-Xs55zt1kIXFIm7Hks83tmn-pTrOY,2743
6
+ rymi/resources/keys.py,sha256=quG-iUiewAHR1I5SRe0n0DrrVMO9c9jACBkEQtgG9qs,597
7
+ rymi/resources/numbers.py,sha256=QC18bTiSUFz9gQUB8NVKXUpPz47htSgcBnPHeKKeZkA,981
8
+ rymi/resources/telephony.py,sha256=z3MxdSDIlnljalTMEjuiqsCgQ5MIfJGuApOY-Earwec,729
9
+ rymi/resources/templates.py,sha256=u3-tVMkmzjBrjjwCV0XXIkHDry4tHS8zlQMFS8dlJKE,409
10
+ rymi/resources/webhooks.py,sha256=t5sCtEIOBU6vE30SnEQxSHU5Nlc19Dr7H0PLNAZKk40,1613
11
+ rymi-1.1.0.dist-info/licenses/LICENSE,sha256=Z1Q9UlBjwLe7vpwpO5tJ9C0ERnX69PjT2Y39hgH_Xrk,1064
12
+ rymi-1.1.0.dist-info/METADATA,sha256=jObAEwYa5sHtDB6PkBoL5Osi8JPdF4BMD-cxkhyHNvw,1689
13
+ rymi-1.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
14
+ rymi-1.1.0.dist-info/top_level.txt,sha256=f0F0RByoASQayVpbn60M1eibY2_gusWuZDzaGklpLT0,5
15
+ rymi-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rymi AI
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 @@
1
+ rymi