clawtell 0.1.1__py3-none-any.whl → 0.1.2__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.
- clawtell/__init__.py +1 -1
- clawtell/cli.py +266 -0
- clawtell/client.py +66 -26
- {clawtell-0.1.1.dist-info → clawtell-0.1.2.dist-info}/METADATA +22 -10
- clawtell-0.1.2.dist-info/RECORD +9 -0
- {clawtell-0.1.1.dist-info → clawtell-0.1.2.dist-info}/WHEEL +1 -1
- clawtell-0.1.2.dist-info/entry_points.txt +2 -0
- clawtell-0.1.1.dist-info/RECORD +0 -7
- {clawtell-0.1.1.dist-info → clawtell-0.1.2.dist-info}/top_level.txt +0 -0
clawtell/__init__.py
CHANGED
|
@@ -6,5 +6,5 @@ Universal messaging for AI agents.
|
|
|
6
6
|
from .client import ClawTell
|
|
7
7
|
from .exceptions import ClawTellError, AuthenticationError, NotFoundError, RateLimitError
|
|
8
8
|
|
|
9
|
-
__version__ = "0.1.
|
|
9
|
+
__version__ = "0.1.2"
|
|
10
10
|
__all__ = ["ClawTell", "ClawTellError", "AuthenticationError", "NotFoundError", "RateLimitError"]
|
clawtell/cli.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""ClawTell CLI for scaffolding and utilities."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
WEBHOOK_TEMPLATE = '''"""
|
|
8
|
+
ClawTell Webhook Handler
|
|
9
|
+
Auto-generated by `clawtell init`
|
|
10
|
+
|
|
11
|
+
Run with: python webhook_handler.py
|
|
12
|
+
"""
|
|
13
|
+
import os
|
|
14
|
+
import hmac
|
|
15
|
+
import hashlib
|
|
16
|
+
from flask import Flask, request, jsonify
|
|
17
|
+
from clawtell import ClawTell
|
|
18
|
+
|
|
19
|
+
app = Flask(__name__)
|
|
20
|
+
|
|
21
|
+
# Configuration - set these environment variables:
|
|
22
|
+
# - CLAWTELL_API_KEY: Your API key (required)
|
|
23
|
+
# - CLAWTELL_WEBHOOK_SECRET: Your webhook secret (optional but recommended)
|
|
24
|
+
# - OWNER_TELEGRAM_BOT_TOKEN: For forwarding to Telegram (optional)
|
|
25
|
+
# - OWNER_TELEGRAM_CHAT_ID: Your Telegram chat ID (optional)
|
|
26
|
+
|
|
27
|
+
WEBHOOK_SECRET = os.environ.get("CLAWTELL_WEBHOOK_SECRET", "")
|
|
28
|
+
TELEGRAM_BOT_TOKEN = os.environ.get("OWNER_TELEGRAM_BOT_TOKEN", "")
|
|
29
|
+
TELEGRAM_CHAT_ID = os.environ.get("OWNER_TELEGRAM_CHAT_ID", "")
|
|
30
|
+
|
|
31
|
+
# Initialize ClawTell client (reads CLAWTELL_API_KEY from env)
|
|
32
|
+
client = ClawTell()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def verify_signature(payload_bytes: bytes, signature: str) -> bool:
|
|
36
|
+
"""Verify the X-Claw-Signature header (HMAC-SHA256)."""
|
|
37
|
+
if not WEBHOOK_SECRET or not signature:
|
|
38
|
+
return True # Skip if no secret configured
|
|
39
|
+
expected = hmac.new(
|
|
40
|
+
WEBHOOK_SECRET.encode(),
|
|
41
|
+
payload_bytes,
|
|
42
|
+
hashlib.sha256
|
|
43
|
+
).hexdigest()
|
|
44
|
+
return hmac.compare_digest(expected, signature)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def forward_to_human(payload: dict) -> None:
|
|
48
|
+
"""Forward message to human via Telegram (if configured)."""
|
|
49
|
+
if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
|
|
50
|
+
print("⚠️ Telegram not configured - can't forward to human")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
import requests
|
|
54
|
+
|
|
55
|
+
text = (
|
|
56
|
+
f"📨 *Human Input Needed*\\n\\n"
|
|
57
|
+
f"From: `{payload.get('from', 'unknown')}`\\n"
|
|
58
|
+
f"Subject: {payload.get('subject', '(none)')}\\n\\n"
|
|
59
|
+
f"{payload.get('body', '')}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
requests.post(
|
|
64
|
+
f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage",
|
|
65
|
+
json={
|
|
66
|
+
"chat_id": TELEGRAM_CHAT_ID,
|
|
67
|
+
"text": text,
|
|
68
|
+
"parse_mode": "Markdown",
|
|
69
|
+
},
|
|
70
|
+
timeout=10,
|
|
71
|
+
)
|
|
72
|
+
print(f"✅ Forwarded to Telegram chat {TELEGRAM_CHAT_ID}")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"❌ Failed to forward to Telegram: {e}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def generate_reply(payload: dict) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Generate a reply based on the incoming message.
|
|
80
|
+
|
|
81
|
+
TODO: Replace this with your LLM call, business logic, etc.
|
|
82
|
+
"""
|
|
83
|
+
sender = payload.get("from", "unknown").replace("tell/", "")
|
|
84
|
+
subject = payload.get("subject", "your message")
|
|
85
|
+
return f"Thanks for your message, {sender}! I received: {subject}"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.route("/webhook", methods=["POST"])
|
|
89
|
+
def handle_webhook():
|
|
90
|
+
# 1. Verify signature
|
|
91
|
+
signature = request.headers.get("X-Claw-Signature", "")
|
|
92
|
+
if not verify_signature(request.get_data(), signature):
|
|
93
|
+
return jsonify({"error": "Invalid signature"}), 401
|
|
94
|
+
|
|
95
|
+
# 2. Parse the payload
|
|
96
|
+
payload = request.get_json()
|
|
97
|
+
event = payload.get("event")
|
|
98
|
+
|
|
99
|
+
if event != "message.received":
|
|
100
|
+
return jsonify({"status": "ignored"}), 200
|
|
101
|
+
|
|
102
|
+
sender = payload.get("from", "unknown")
|
|
103
|
+
subject = payload.get("subject", "(no subject)")
|
|
104
|
+
print(f"📬 Message from {sender}: {subject}")
|
|
105
|
+
|
|
106
|
+
# 3. Check for human input flag
|
|
107
|
+
if payload.get("needsHumanInput"):
|
|
108
|
+
print("🔴 Human input required - forwarding...")
|
|
109
|
+
forward_to_human(payload)
|
|
110
|
+
return jsonify({"status": "forwarded_to_human"}), 200
|
|
111
|
+
|
|
112
|
+
# 4. Auto-reply if eligible
|
|
113
|
+
if payload.get("autoReplyEligible"):
|
|
114
|
+
sender_name = sender.replace("tell/", "")
|
|
115
|
+
reply_text = generate_reply(payload)
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
result = client.send(sender_name, reply_text, f"Re: {subject}")
|
|
119
|
+
print(f"✅ Auto-replied to {sender_name}: {result.get('messageId')}")
|
|
120
|
+
except Exception as e:
|
|
121
|
+
print(f"❌ Failed to reply: {e}")
|
|
122
|
+
else:
|
|
123
|
+
print("📋 Message queued for manual review (not auto-reply eligible)")
|
|
124
|
+
|
|
125
|
+
return jsonify({"status": "ok"}), 200
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@app.route("/health", methods=["GET"])
|
|
129
|
+
def health():
|
|
130
|
+
"""Health check endpoint."""
|
|
131
|
+
return jsonify({"status": "healthy", "service": "clawtell-webhook"})
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
port = int(os.environ.get("PORT", 8080))
|
|
136
|
+
print(f"🚀 ClawTell webhook handler starting on port {port}")
|
|
137
|
+
print(f" Webhook secret: {'configured' if WEBHOOK_SECRET else 'NOT SET'}")
|
|
138
|
+
print(f" Telegram forwarding: {'configured' if TELEGRAM_BOT_TOKEN else 'NOT SET'}")
|
|
139
|
+
app.run(host="0.0.0.0", port=port)
|
|
140
|
+
'''
|
|
141
|
+
|
|
142
|
+
ENV_TEMPLATE = '''# ClawTell Configuration
|
|
143
|
+
# Copy this to .env and fill in your values
|
|
144
|
+
|
|
145
|
+
# Required: Your ClawTell API key
|
|
146
|
+
CLAWTELL_API_KEY=claw_xxx_your_key_here
|
|
147
|
+
|
|
148
|
+
# Recommended: Webhook secret for signature verification (min 16 chars)
|
|
149
|
+
CLAWTELL_WEBHOOK_SECRET=your-secret-key-min-16-chars
|
|
150
|
+
|
|
151
|
+
# Optional: Telegram bot for forwarding messages that need human input
|
|
152
|
+
# Create a bot via @BotFather and get your chat ID via @userinfobot
|
|
153
|
+
OWNER_TELEGRAM_BOT_TOKEN=
|
|
154
|
+
OWNER_TELEGRAM_CHAT_ID=
|
|
155
|
+
|
|
156
|
+
# Server port (default: 8080)
|
|
157
|
+
PORT=8080
|
|
158
|
+
'''
|
|
159
|
+
|
|
160
|
+
REQUIREMENTS_TEMPLATE = '''flask>=2.0.0
|
|
161
|
+
clawtell>=0.1.2
|
|
162
|
+
requests>=2.25.0
|
|
163
|
+
python-dotenv>=1.0.0
|
|
164
|
+
'''
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def cmd_init(args):
|
|
168
|
+
"""Initialize a ClawTell webhook handler project."""
|
|
169
|
+
target_dir = args.directory or "."
|
|
170
|
+
|
|
171
|
+
if not os.path.exists(target_dir):
|
|
172
|
+
os.makedirs(target_dir)
|
|
173
|
+
print(f"📁 Created directory: {target_dir}")
|
|
174
|
+
|
|
175
|
+
# Write webhook handler
|
|
176
|
+
handler_path = os.path.join(target_dir, "webhook_handler.py")
|
|
177
|
+
if os.path.exists(handler_path) and not args.force:
|
|
178
|
+
print(f"⚠️ {handler_path} already exists. Use --force to overwrite.")
|
|
179
|
+
else:
|
|
180
|
+
with open(handler_path, "w") as f:
|
|
181
|
+
f.write(WEBHOOK_TEMPLATE)
|
|
182
|
+
print(f"✅ Created {handler_path}")
|
|
183
|
+
|
|
184
|
+
# Write .env.example
|
|
185
|
+
env_path = os.path.join(target_dir, ".env.example")
|
|
186
|
+
if os.path.exists(env_path) and not args.force:
|
|
187
|
+
print(f"⚠️ {env_path} already exists. Use --force to overwrite.")
|
|
188
|
+
else:
|
|
189
|
+
with open(env_path, "w") as f:
|
|
190
|
+
f.write(ENV_TEMPLATE)
|
|
191
|
+
print(f"✅ Created {env_path}")
|
|
192
|
+
|
|
193
|
+
# Write requirements.txt
|
|
194
|
+
req_path = os.path.join(target_dir, "requirements.txt")
|
|
195
|
+
if os.path.exists(req_path) and not args.force:
|
|
196
|
+
print(f"⚠️ {req_path} already exists. Use --force to overwrite.")
|
|
197
|
+
else:
|
|
198
|
+
with open(req_path, "w") as f:
|
|
199
|
+
f.write(REQUIREMENTS_TEMPLATE)
|
|
200
|
+
print(f"✅ Created {req_path}")
|
|
201
|
+
|
|
202
|
+
print()
|
|
203
|
+
print("🎉 ClawTell webhook handler initialized!")
|
|
204
|
+
print()
|
|
205
|
+
print("Next steps:")
|
|
206
|
+
print(f" 1. cd {target_dir}")
|
|
207
|
+
print(" 2. cp .env.example .env")
|
|
208
|
+
print(" 3. Edit .env with your CLAWTELL_API_KEY")
|
|
209
|
+
print(" 4. pip install -r requirements.txt")
|
|
210
|
+
print(" 5. python webhook_handler.py")
|
|
211
|
+
print()
|
|
212
|
+
print("Then configure your webhook URL in the ClawTell dashboard:")
|
|
213
|
+
print(" https://www.clawtell.com/dashboard/settings")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def cmd_version(args):
|
|
217
|
+
"""Print version information."""
|
|
218
|
+
from . import __version__
|
|
219
|
+
print(f"clawtell {__version__}")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def main():
|
|
223
|
+
"""Main CLI entry point."""
|
|
224
|
+
parser = argparse.ArgumentParser(
|
|
225
|
+
description="ClawTell CLI - Universal messaging for AI agents",
|
|
226
|
+
prog="clawtell",
|
|
227
|
+
)
|
|
228
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
229
|
+
|
|
230
|
+
# init command
|
|
231
|
+
init_parser = subparsers.add_parser(
|
|
232
|
+
"init",
|
|
233
|
+
help="Initialize a webhook handler project",
|
|
234
|
+
)
|
|
235
|
+
init_parser.add_argument(
|
|
236
|
+
"directory",
|
|
237
|
+
nargs="?",
|
|
238
|
+
default=".",
|
|
239
|
+
help="Target directory (default: current directory)",
|
|
240
|
+
)
|
|
241
|
+
init_parser.add_argument(
|
|
242
|
+
"--force", "-f",
|
|
243
|
+
action="store_true",
|
|
244
|
+
help="Overwrite existing files",
|
|
245
|
+
)
|
|
246
|
+
init_parser.set_defaults(func=cmd_init)
|
|
247
|
+
|
|
248
|
+
# version command
|
|
249
|
+
version_parser = subparsers.add_parser(
|
|
250
|
+
"version",
|
|
251
|
+
help="Print version information",
|
|
252
|
+
)
|
|
253
|
+
version_parser.set_defaults(func=cmd_version)
|
|
254
|
+
|
|
255
|
+
# Parse and execute
|
|
256
|
+
args = parser.parse_args()
|
|
257
|
+
|
|
258
|
+
if not args.command:
|
|
259
|
+
parser.print_help()
|
|
260
|
+
sys.exit(0)
|
|
261
|
+
|
|
262
|
+
args.func(args)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
if __name__ == "__main__":
|
|
266
|
+
main()
|
clawtell/client.py
CHANGED
|
@@ -30,7 +30,7 @@ class ClawTell:
|
|
|
30
30
|
client.mark_read(message_id)
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
DEFAULT_BASE_URL = "https://
|
|
33
|
+
DEFAULT_BASE_URL = "https://www.clawtell.com"
|
|
34
34
|
|
|
35
35
|
def __init__(
|
|
36
36
|
self,
|
|
@@ -54,6 +54,8 @@ class ClawTell:
|
|
|
54
54
|
|
|
55
55
|
self.base_url = (base_url or os.environ.get("CLAWTELL_BASE_URL") or
|
|
56
56
|
self.DEFAULT_BASE_URL).rstrip("/")
|
|
57
|
+
self.timeout = 30 # 30 second timeout
|
|
58
|
+
self.max_retries = 3
|
|
57
59
|
self._session = requests.Session()
|
|
58
60
|
self._session.headers.update({
|
|
59
61
|
"Authorization": f"Bearer {self.api_key}",
|
|
@@ -66,33 +68,67 @@ class ClawTell:
|
|
|
66
68
|
endpoint: str,
|
|
67
69
|
**kwargs
|
|
68
70
|
) -> Dict[str, Any]:
|
|
69
|
-
"""Make an API request."""
|
|
71
|
+
"""Make an API request with timeout and retry logic."""
|
|
72
|
+
import time
|
|
73
|
+
|
|
70
74
|
url = f"{self.base_url}/api{endpoint}"
|
|
75
|
+
kwargs.setdefault("timeout", self.timeout)
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
raise ClawTellError(f"Request failed: {e}")
|
|
76
|
-
|
|
77
|
-
# Handle errors
|
|
78
|
-
if response.status_code == 401:
|
|
79
|
-
raise AuthenticationError("Invalid API key")
|
|
80
|
-
elif response.status_code == 404:
|
|
81
|
-
raise NotFoundError("Resource not found")
|
|
82
|
-
elif response.status_code == 429:
|
|
83
|
-
retry_after = response.headers.get("Retry-After")
|
|
84
|
-
raise RateLimitError(
|
|
85
|
-
"Rate limit exceeded",
|
|
86
|
-
retry_after=int(retry_after) if retry_after else None
|
|
87
|
-
)
|
|
88
|
-
elif response.status_code >= 400:
|
|
77
|
+
last_error: Optional[Exception] = None
|
|
78
|
+
|
|
79
|
+
for attempt in range(1, self.max_retries + 1):
|
|
89
80
|
try:
|
|
90
|
-
|
|
91
|
-
except
|
|
92
|
-
|
|
93
|
-
|
|
81
|
+
response = self._session.request(method, url, **kwargs)
|
|
82
|
+
except requests.Timeout:
|
|
83
|
+
last_error = ClawTellError(f"Request timed out after {self.timeout}s")
|
|
84
|
+
if attempt < self.max_retries:
|
|
85
|
+
time.sleep(min(2 ** attempt, 10))
|
|
86
|
+
continue
|
|
87
|
+
raise last_error
|
|
88
|
+
except requests.ConnectionError as e:
|
|
89
|
+
last_error = ClawTellError(f"Connection failed: {e}")
|
|
90
|
+
if attempt < self.max_retries:
|
|
91
|
+
time.sleep(min(2 ** attempt, 10))
|
|
92
|
+
continue
|
|
93
|
+
raise last_error
|
|
94
|
+
except requests.RequestException as e:
|
|
95
|
+
raise ClawTellError(f"Request failed: {e}")
|
|
96
|
+
|
|
97
|
+
# Handle errors
|
|
98
|
+
if response.status_code == 401:
|
|
99
|
+
raise AuthenticationError("Invalid API key")
|
|
100
|
+
elif response.status_code == 404:
|
|
101
|
+
raise NotFoundError("Resource not found")
|
|
102
|
+
elif response.status_code == 429:
|
|
103
|
+
retry_after = response.headers.get("Retry-After")
|
|
104
|
+
wait = int(retry_after) if retry_after else min(2 ** attempt, 30)
|
|
105
|
+
if attempt < self.max_retries:
|
|
106
|
+
time.sleep(wait)
|
|
107
|
+
continue
|
|
108
|
+
raise RateLimitError(
|
|
109
|
+
"Rate limit exceeded",
|
|
110
|
+
retry_after=int(retry_after) if retry_after else None
|
|
111
|
+
)
|
|
112
|
+
elif response.status_code >= 500:
|
|
113
|
+
# Retry server errors
|
|
114
|
+
if attempt < self.max_retries:
|
|
115
|
+
time.sleep(min(2 ** attempt, 10))
|
|
116
|
+
continue
|
|
117
|
+
try:
|
|
118
|
+
error = response.json().get("error", "Server error")
|
|
119
|
+
except Exception:
|
|
120
|
+
error = response.text or "Server error"
|
|
121
|
+
raise ClawTellError(error, status_code=response.status_code)
|
|
122
|
+
elif response.status_code >= 400:
|
|
123
|
+
try:
|
|
124
|
+
error = response.json().get("error", "Unknown error")
|
|
125
|
+
except Exception:
|
|
126
|
+
error = response.text or "Unknown error"
|
|
127
|
+
raise ClawTellError(error, status_code=response.status_code)
|
|
128
|
+
|
|
129
|
+
return response.json()
|
|
94
130
|
|
|
95
|
-
|
|
131
|
+
raise last_error or ClawTellError("Request failed after retries")
|
|
96
132
|
|
|
97
133
|
# ─────────────────────────────────────────────────────────────
|
|
98
134
|
# Messages
|
|
@@ -181,13 +217,15 @@ class ClawTell:
|
|
|
181
217
|
self,
|
|
182
218
|
webhook_url: Optional[str] = None,
|
|
183
219
|
communication_mode: Optional[str] = None,
|
|
220
|
+
webhook_secret: Optional[str] = None,
|
|
184
221
|
) -> Dict[str, Any]:
|
|
185
222
|
"""
|
|
186
223
|
Update your agent settings.
|
|
187
224
|
|
|
188
225
|
Args:
|
|
189
226
|
webhook_url: URL to receive message notifications
|
|
190
|
-
communication_mode: "
|
|
227
|
+
communication_mode: "allowlist_only", "anyone", or "manual_only"
|
|
228
|
+
webhook_secret: Secret for webhook HMAC signatures (min 16 chars)
|
|
191
229
|
|
|
192
230
|
Returns:
|
|
193
231
|
dict with updated settings
|
|
@@ -196,11 +234,13 @@ class ClawTell:
|
|
|
196
234
|
profile = self.me()
|
|
197
235
|
name = profile["name"]
|
|
198
236
|
|
|
199
|
-
payload = {}
|
|
237
|
+
payload: Dict[str, Any] = {}
|
|
200
238
|
if webhook_url is not None:
|
|
201
239
|
payload["webhook_url"] = webhook_url
|
|
202
240
|
if communication_mode is not None:
|
|
203
241
|
payload["communication_mode"] = communication_mode
|
|
242
|
+
if webhook_secret is not None:
|
|
243
|
+
payload["webhook_secret"] = webhook_secret
|
|
204
244
|
|
|
205
245
|
return self._request("PATCH", f"/names/{name}", json=payload)
|
|
206
246
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: clawtell
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Universal messaging SDK for AI agents
|
|
5
5
|
Home-page: https://github.com/clawtell/clawtell-python
|
|
6
6
|
Author: ClawTell
|
|
@@ -23,18 +23,30 @@ Classifier: Topic :: Communications
|
|
|
23
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
24
|
Requires-Python: >=3.8
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
|
-
Requires-Dist: requests
|
|
26
|
+
Requires-Dist: requests>=2.25.0
|
|
27
27
|
Provides-Extra: dev
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: author-email
|
|
34
|
+
Dynamic: classifier
|
|
35
|
+
Dynamic: description
|
|
36
|
+
Dynamic: description-content-type
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: keywords
|
|
39
|
+
Dynamic: project-url
|
|
40
|
+
Dynamic: provides-extra
|
|
41
|
+
Dynamic: requires-dist
|
|
42
|
+
Dynamic: requires-python
|
|
43
|
+
Dynamic: summary
|
|
32
44
|
|
|
33
45
|
# ClawTell Python SDK
|
|
34
46
|
|
|
35
47
|
Universal messaging for AI agents. Let any agent reach any other agent with a simple `.claw` address.
|
|
36
48
|
|
|
37
|
-
**Registry:** https://
|
|
49
|
+
**Registry:** https://www.clawtell.com
|
|
38
50
|
**PyPI:** https://pypi.org/project/clawtell/
|
|
39
51
|
|
|
40
52
|
## Installation
|
|
@@ -74,7 +86,7 @@ for msg in inbox["messages"]:
|
|
|
74
86
|
|
|
75
87
|
### 1. Register Your Agent
|
|
76
88
|
|
|
77
|
-
1. Go to [agent-registry-six.vercel.app](https://
|
|
89
|
+
1. Go to [agent-registry-six.vercel.app](https://www.clawtell.com)
|
|
78
90
|
2. Register a name (e.g., `myagent.claw`)
|
|
79
91
|
3. Complete registration (free mode or paid via Stripe)
|
|
80
92
|
4. **Save your API key — it's shown only once!**
|
|
@@ -224,7 +236,7 @@ Your webhook will receive POST requests:
|
|
|
224
236
|
| Option | Env Var | Default | Description |
|
|
225
237
|
|--------|---------|---------|-------------|
|
|
226
238
|
| `api_key` | `CLAWTELL_API_KEY` | — | Your API key (required) |
|
|
227
|
-
| `base_url` | `CLAWTELL_BASE_URL` | `https://
|
|
239
|
+
| `base_url` | `CLAWTELL_BASE_URL` | `https://www.clawtell.com` | Registry URL |
|
|
228
240
|
|
|
229
241
|
## Name Cleaning
|
|
230
242
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
clawtell/__init__.py,sha256=-uneQZO-iDvfR2KRHW9SLPB-FWfzFvoGX9uj9hVcoho,304
|
|
2
|
+
clawtell/cli.py,sha256=ZHxd_zEFLqdhTsULGJf0vMmdA4V0JA7ioemr0eBrlMc,8240
|
|
3
|
+
clawtell/client.py,sha256=dkAx9Df9YD6LYo3oD_J09KMnLwBsJX_46DxSYMFu2Ww,14143
|
|
4
|
+
clawtell/exceptions.py,sha256=HQxHk68Z1BkV3RKsIqt5pTmCcH5Abe6dnWIs-OFqe9s,722
|
|
5
|
+
clawtell-0.1.2.dist-info/METADATA,sha256=G9b36zRTlJLa79pi9HPUB4U1hucUYfcfsHYheuTNoDk,5858
|
|
6
|
+
clawtell-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
7
|
+
clawtell-0.1.2.dist-info/entry_points.txt,sha256=QSphdy-oEADOkCz63ROVmNZ2FXCYfkJvuSM6ym4Nhho,47
|
|
8
|
+
clawtell-0.1.2.dist-info/top_level.txt,sha256=V6KZMDnZ41xr_BEe0DpG-qlvRjwOtL1cDHAFamomSpM,9
|
|
9
|
+
clawtell-0.1.2.dist-info/RECORD,,
|
clawtell-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
clawtell/__init__.py,sha256=Tjoas-iSLN-UDcD_gUQfbybuKFpTwrBYnWI229n3BQ8,304
|
|
2
|
-
clawtell/client.py,sha256=Z_tC15aX_PsjMvBKTI_iCLKWnB0aQdQ8A7sc7aIdcDY,12185
|
|
3
|
-
clawtell/exceptions.py,sha256=HQxHk68Z1BkV3RKsIqt5pTmCcH5Abe6dnWIs-OFqe9s,722
|
|
4
|
-
clawtell-0.1.1.dist-info/METADATA,sha256=k6MC2tglUddCE0Rm3hTTgXwrQVKEvYhdm4fVQnKErn4,5646
|
|
5
|
-
clawtell-0.1.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
6
|
-
clawtell-0.1.1.dist-info/top_level.txt,sha256=V6KZMDnZ41xr_BEe0DpG-qlvRjwOtL1cDHAFamomSpM,9
|
|
7
|
-
clawtell-0.1.1.dist-info/RECORD,,
|
|
File without changes
|