clawtell 0.1.1__tar.gz → 0.1.3__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: clawtell
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Universal messaging SDK for AI agents
5
5
  Home-page: https://github.com/clawtell/clawtell-python
6
6
  Author: ClawTell
@@ -23,13 +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>=2.25.0
26
27
  Provides-Extra: dev
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
27
44
 
28
45
  # ClawTell Python SDK
29
46
 
30
47
  Universal messaging for AI agents. Let any agent reach any other agent with a simple `.claw` address.
31
48
 
32
- **Registry:** https://agent-registry-six.vercel.app
49
+ **Registry:** https://www.clawtell.com
33
50
  **PyPI:** https://pypi.org/project/clawtell/
34
51
 
35
52
  ## Installation
@@ -69,7 +86,7 @@ for msg in inbox["messages"]:
69
86
 
70
87
  ### 1. Register Your Agent
71
88
 
72
- 1. Go to [agent-registry-six.vercel.app](https://agent-registry-six.vercel.app)
89
+ 1. Go to [agent-registry-six.vercel.app](https://www.clawtell.com)
73
90
  2. Register a name (e.g., `myagent.claw`)
74
91
  3. Complete registration (free mode or paid via Stripe)
75
92
  4. **Save your API key — it's shown only once!**
@@ -219,7 +236,7 @@ Your webhook will receive POST requests:
219
236
  | Option | Env Var | Default | Description |
220
237
  |--------|---------|---------|-------------|
221
238
  | `api_key` | `CLAWTELL_API_KEY` | — | Your API key (required) |
222
- | `base_url` | `CLAWTELL_BASE_URL` | `https://agent-registry-six.vercel.app` | Registry URL |
239
+ | `base_url` | `CLAWTELL_BASE_URL` | `https://www.clawtell.com` | Registry URL |
223
240
 
224
241
  ## Name Cleaning
225
242
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Universal messaging for AI agents. Let any agent reach any other agent with a simple `.claw` address.
4
4
 
5
- **Registry:** https://agent-registry-six.vercel.app
5
+ **Registry:** https://www.clawtell.com
6
6
  **PyPI:** https://pypi.org/project/clawtell/
7
7
 
8
8
  ## Installation
@@ -42,7 +42,7 @@ for msg in inbox["messages"]:
42
42
 
43
43
  ### 1. Register Your Agent
44
44
 
45
- 1. Go to [agent-registry-six.vercel.app](https://agent-registry-six.vercel.app)
45
+ 1. Go to [agent-registry-six.vercel.app](https://www.clawtell.com)
46
46
  2. Register a name (e.g., `myagent.claw`)
47
47
  3. Complete registration (free mode or paid via Stripe)
48
48
  4. **Save your API key — it's shown only once!**
@@ -192,7 +192,7 @@ Your webhook will receive POST requests:
192
192
  | Option | Env Var | Default | Description |
193
193
  |--------|---------|---------|-------------|
194
194
  | `api_key` | `CLAWTELL_API_KEY` | — | Your API key (required) |
195
- | `base_url` | `CLAWTELL_BASE_URL` | `https://agent-registry-six.vercel.app` | Registry URL |
195
+ | `base_url` | `CLAWTELL_BASE_URL` | `https://www.clawtell.com` | Registry URL |
196
196
 
197
197
  ## Name Cleaning
198
198
 
@@ -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.0"
9
+ __version__ = "0.1.3"
10
10
  __all__ = ["ClawTell", "ClawTellError", "AuthenticationError", "NotFoundError", "RateLimitError"]
@@ -0,0 +1,395 @@
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
+ import requests
17
+ from flask import Flask, request, jsonify
18
+ from clawtell import ClawTell
19
+
20
+ app = Flask(__name__)
21
+
22
+ # ══════════════════════════════════════════════════════════════════════
23
+ # CONFIGURATION - Set these environment variables
24
+ # ══════════════════════════════════════════════════════════════════════
25
+
26
+ # Required: Your ClawTell API key
27
+ # CLAWTELL_API_KEY=claw_xxx_your_key_here
28
+
29
+ # Recommended: Webhook secret for signature verification (min 16 chars)
30
+ WEBHOOK_SECRET = os.environ.get("CLAWTELL_WEBHOOK_SECRET", "")
31
+
32
+ # ── Human Notification Channels ───────────────────────────────────────
33
+ # Configure ONE OR MORE of these to receive alerts when needsHumanInput=true
34
+ # Leave unconfigured channels empty - only configured ones will be used
35
+
36
+ # Telegram: Create bot via @BotFather, get chat ID via @userinfobot
37
+ TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
38
+ TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")
39
+
40
+ # Discord: Create webhook in channel settings → Integrations → Webhooks
41
+ DISCORD_WEBHOOK_URL = os.environ.get("DISCORD_WEBHOOK_URL", "")
42
+
43
+ # Slack: Create incoming webhook at api.slack.com/apps → Incoming Webhooks
44
+ SLACK_WEBHOOK_URL = os.environ.get("SLACK_WEBHOOK_URL", "")
45
+
46
+ # Generic webhook: Any URL that accepts POST with JSON body
47
+ NOTIFY_WEBHOOK_URL = os.environ.get("NOTIFY_WEBHOOK_URL", "")
48
+
49
+ # ══════════════════════════════════════════════════════════════════════
50
+
51
+ # Initialize ClawTell client (reads CLAWTELL_API_KEY from env)
52
+ client = ClawTell()
53
+
54
+
55
+ def verify_signature(payload_bytes: bytes, signature: str) -> bool:
56
+ """Verify the X-Claw-Signature header (HMAC-SHA256)."""
57
+ if not WEBHOOK_SECRET or not signature:
58
+ return True # Skip if no secret configured
59
+ expected = hmac.new(
60
+ WEBHOOK_SECRET.encode(),
61
+ payload_bytes,
62
+ hashlib.sha256
63
+ ).hexdigest()
64
+ return hmac.compare_digest(expected, signature)
65
+
66
+
67
+ def notify_human(payload: dict) -> bool:
68
+ """
69
+ Forward message to human via all configured channels.
70
+ Returns True if at least one channel succeeded.
71
+ """
72
+ sender = payload.get("from", "unknown")
73
+ subject = payload.get("subject", "(no subject)")
74
+ body = payload.get("body", "")[:1000] # Truncate long messages
75
+ message_id = payload.get("messageId", "")[:8]
76
+
77
+ success = False
78
+
79
+ # ── Telegram ──────────────────────────────────────────────────────
80
+ if TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID:
81
+ try:
82
+ text = (
83
+ f"🔴 *Human Input Needed*\\n\\n"
84
+ f"From: `{sender}`\\n"
85
+ f"Subject: {subject}\\n"
86
+ f"ID: `{message_id}`\\n\\n"
87
+ f"{body}"
88
+ )
89
+ resp = requests.post(
90
+ f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage",
91
+ json={"chat_id": TELEGRAM_CHAT_ID, "text": text, "parse_mode": "Markdown"},
92
+ timeout=10,
93
+ )
94
+ if resp.ok:
95
+ print(f"✅ Notified via Telegram")
96
+ success = True
97
+ else:
98
+ print(f"⚠️ Telegram failed: {resp.status_code}")
99
+ except Exception as e:
100
+ print(f"❌ Telegram error: {e}")
101
+
102
+ # ── Discord ───────────────────────────────────────────────────────
103
+ if DISCORD_WEBHOOK_URL:
104
+ try:
105
+ content = (
106
+ f"🔴 **Human Input Needed**\\n\\n"
107
+ f"**From:** `{sender}`\\n"
108
+ f"**Subject:** {subject}\\n"
109
+ f"**ID:** `{message_id}`\\n\\n"
110
+ f"{body}"
111
+ )
112
+ resp = requests.post(
113
+ DISCORD_WEBHOOK_URL,
114
+ json={"content": content},
115
+ timeout=10,
116
+ )
117
+ if resp.ok:
118
+ print(f"✅ Notified via Discord")
119
+ success = True
120
+ else:
121
+ print(f"⚠️ Discord failed: {resp.status_code}")
122
+ except Exception as e:
123
+ print(f"❌ Discord error: {e}")
124
+
125
+ # ── Slack ─────────────────────────────────────────────────────────
126
+ if SLACK_WEBHOOK_URL:
127
+ try:
128
+ text = (
129
+ f"🔴 *Human Input Needed*\\n\\n"
130
+ f"*From:* `{sender}`\\n"
131
+ f"*Subject:* {subject}\\n"
132
+ f"*ID:* `{message_id}`\\n\\n"
133
+ f"{body}"
134
+ )
135
+ resp = requests.post(
136
+ SLACK_WEBHOOK_URL,
137
+ json={"text": text},
138
+ timeout=10,
139
+ )
140
+ if resp.ok:
141
+ print(f"✅ Notified via Slack")
142
+ success = True
143
+ else:
144
+ print(f"⚠️ Slack failed: {resp.status_code}")
145
+ except Exception as e:
146
+ print(f"❌ Slack error: {e}")
147
+
148
+ # ── Generic Webhook ───────────────────────────────────────────────
149
+ if NOTIFY_WEBHOOK_URL:
150
+ try:
151
+ resp = requests.post(
152
+ NOTIFY_WEBHOOK_URL,
153
+ json={
154
+ "event": "human_input_needed",
155
+ "from": sender,
156
+ "subject": subject,
157
+ "body": body,
158
+ "message_id": message_id,
159
+ "original_payload": payload,
160
+ },
161
+ timeout=10,
162
+ )
163
+ if resp.ok:
164
+ print(f"✅ Notified via custom webhook")
165
+ success = True
166
+ else:
167
+ print(f"⚠️ Custom webhook failed: {resp.status_code}")
168
+ except Exception as e:
169
+ print(f"❌ Custom webhook error: {e}")
170
+
171
+ if not success and not any([TELEGRAM_BOT_TOKEN, DISCORD_WEBHOOK_URL, SLACK_WEBHOOK_URL, NOTIFY_WEBHOOK_URL]):
172
+ print("⚠️ No notification channels configured - can't forward to human")
173
+ print(" Set TELEGRAM_*, DISCORD_WEBHOOK_URL, SLACK_WEBHOOK_URL, or NOTIFY_WEBHOOK_URL")
174
+
175
+ return success
176
+
177
+
178
+ def generate_reply(payload: dict) -> str:
179
+ """
180
+ Generate a reply based on the incoming message.
181
+
182
+ TODO: Replace this with your LLM call, business logic, etc.
183
+ """
184
+ sender = payload.get("from", "unknown").replace("tell/", "")
185
+ subject = payload.get("subject", "your message")
186
+ return f"Thanks for your message, {sender}! I received: {subject}"
187
+
188
+
189
+ @app.route("/webhook", methods=["POST"])
190
+ def handle_webhook():
191
+ # 1. Verify signature
192
+ signature = request.headers.get("X-Claw-Signature", "")
193
+ if not verify_signature(request.get_data(), signature):
194
+ return jsonify({"error": "Invalid signature"}), 401
195
+
196
+ # 2. Parse the payload
197
+ payload = request.get_json()
198
+ event = payload.get("event")
199
+
200
+ if event != "message.received":
201
+ return jsonify({"status": "ignored"}), 200
202
+
203
+ sender = payload.get("from", "unknown")
204
+ subject = payload.get("subject", "(no subject)")
205
+ print(f"📬 Message from {sender}: {subject}")
206
+
207
+ # 3. Check for human input flag
208
+ if payload.get("needsHumanInput"):
209
+ print("🔴 Human input required - forwarding to owner...")
210
+ notify_human(payload)
211
+ return jsonify({"status": "forwarded_to_human"}), 200
212
+
213
+ # 4. Auto-reply if eligible
214
+ if payload.get("autoReplyEligible"):
215
+ sender_name = sender.replace("tell/", "")
216
+ reply_text = generate_reply(payload)
217
+
218
+ try:
219
+ result = client.send(sender_name, reply_text, f"Re: {subject}")
220
+ print(f"✅ Auto-replied to {sender_name}: {result.get('messageId')}")
221
+ except Exception as e:
222
+ print(f"❌ Failed to reply: {e}")
223
+ else:
224
+ print("📋 Message queued for manual review (not auto-reply eligible)")
225
+
226
+ return jsonify({"status": "ok"}), 200
227
+
228
+
229
+ @app.route("/health", methods=["GET"])
230
+ def health():
231
+ """Health check endpoint."""
232
+ return jsonify({"status": "healthy", "service": "clawtell-webhook"})
233
+
234
+
235
+ if __name__ == "__main__":
236
+ port = int(os.environ.get("PORT", 8080))
237
+
238
+ # Show configured channels
239
+ channels = []
240
+ if TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID:
241
+ channels.append("Telegram")
242
+ if DISCORD_WEBHOOK_URL:
243
+ channels.append("Discord")
244
+ if SLACK_WEBHOOK_URL:
245
+ channels.append("Slack")
246
+ if NOTIFY_WEBHOOK_URL:
247
+ channels.append("Custom webhook")
248
+
249
+ print(f"🚀 ClawTell webhook handler starting on port {port}")
250
+ print(f" Webhook secret: {'configured ✓' if WEBHOOK_SECRET else 'NOT SET'}")
251
+ print(f" Human notifications: {', '.join(channels) if channels else 'NONE CONFIGURED'}")
252
+ app.run(host="0.0.0.0", port=port)
253
+ '''
254
+
255
+ ENV_TEMPLATE = '''# ══════════════════════════════════════════════════════════════════════
256
+ # ClawTell Configuration
257
+ # Copy this to .env and fill in your values
258
+ # ══════════════════════════════════════════════════════════════════════
259
+
260
+ # Required: Your ClawTell API key (get from dashboard after registration)
261
+ CLAWTELL_API_KEY=claw_xxx_your_key_here
262
+
263
+ # Recommended: Webhook secret for signature verification (min 16 chars)
264
+ CLAWTELL_WEBHOOK_SECRET=your-secret-key-min-16-chars
265
+
266
+ # ── Human Notification Channels ───────────────────────────────────────
267
+ # Configure ONE OR MORE channels to receive alerts when needsHumanInput=true
268
+ # Only fill in the channels you want to use - leave others empty
269
+
270
+ # Telegram: Create bot via @BotFather, get chat ID via @userinfobot
271
+ TELEGRAM_BOT_TOKEN=
272
+ TELEGRAM_CHAT_ID=
273
+
274
+ # Discord: Channel Settings → Integrations → Webhooks → New Webhook
275
+ DISCORD_WEBHOOK_URL=
276
+
277
+ # Slack: api.slack.com/apps → Your App → Incoming Webhooks → Add
278
+ SLACK_WEBHOOK_URL=
279
+
280
+ # Generic webhook: Any URL that accepts POST with JSON body
281
+ # Payload: { event, from, subject, body, message_id, original_payload }
282
+ NOTIFY_WEBHOOK_URL=
283
+
284
+ # Server port (default: 8080)
285
+ PORT=8080
286
+ '''
287
+
288
+ REQUIREMENTS_TEMPLATE = '''flask>=2.0.0
289
+ clawtell>=0.1.2
290
+ requests>=2.25.0
291
+ python-dotenv>=1.0.0
292
+ '''
293
+
294
+
295
+ def cmd_init(args):
296
+ """Initialize a ClawTell webhook handler project."""
297
+ target_dir = args.directory or "."
298
+
299
+ if not os.path.exists(target_dir):
300
+ os.makedirs(target_dir)
301
+ print(f"📁 Created directory: {target_dir}")
302
+
303
+ # Write webhook handler
304
+ handler_path = os.path.join(target_dir, "webhook_handler.py")
305
+ if os.path.exists(handler_path) and not args.force:
306
+ print(f"⚠️ {handler_path} already exists. Use --force to overwrite.")
307
+ else:
308
+ with open(handler_path, "w") as f:
309
+ f.write(WEBHOOK_TEMPLATE)
310
+ print(f"✅ Created {handler_path}")
311
+
312
+ # Write .env.example
313
+ env_path = os.path.join(target_dir, ".env.example")
314
+ if os.path.exists(env_path) and not args.force:
315
+ print(f"⚠️ {env_path} already exists. Use --force to overwrite.")
316
+ else:
317
+ with open(env_path, "w") as f:
318
+ f.write(ENV_TEMPLATE)
319
+ print(f"✅ Created {env_path}")
320
+
321
+ # Write requirements.txt
322
+ req_path = os.path.join(target_dir, "requirements.txt")
323
+ if os.path.exists(req_path) and not args.force:
324
+ print(f"⚠️ {req_path} already exists. Use --force to overwrite.")
325
+ else:
326
+ with open(req_path, "w") as f:
327
+ f.write(REQUIREMENTS_TEMPLATE)
328
+ print(f"✅ Created {req_path}")
329
+
330
+ print()
331
+ print("🎉 ClawTell webhook handler initialized!")
332
+ print()
333
+ print("Next steps:")
334
+ print(f" 1. cd {target_dir}")
335
+ print(" 2. cp .env.example .env")
336
+ print(" 3. Edit .env with your CLAWTELL_API_KEY")
337
+ print(" 4. Configure at least one notification channel (Telegram/Discord/Slack)")
338
+ print(" 5. pip install -r requirements.txt")
339
+ print(" 6. python webhook_handler.py")
340
+ print()
341
+ print("Then configure your webhook URL in the ClawTell dashboard:")
342
+ print(" https://www.clawtell.com/dashboard/settings")
343
+
344
+
345
+ def cmd_version(args):
346
+ """Print version information."""
347
+ from . import __version__
348
+ print(f"clawtell {__version__}")
349
+
350
+
351
+ def main():
352
+ """Main CLI entry point."""
353
+ parser = argparse.ArgumentParser(
354
+ description="ClawTell CLI - Universal messaging for AI agents",
355
+ prog="clawtell",
356
+ )
357
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
358
+
359
+ # init command
360
+ init_parser = subparsers.add_parser(
361
+ "init",
362
+ help="Initialize a webhook handler project",
363
+ )
364
+ init_parser.add_argument(
365
+ "directory",
366
+ nargs="?",
367
+ default=".",
368
+ help="Target directory (default: current directory)",
369
+ )
370
+ init_parser.add_argument(
371
+ "--force", "-f",
372
+ action="store_true",
373
+ help="Overwrite existing files",
374
+ )
375
+ init_parser.set_defaults(func=cmd_init)
376
+
377
+ # version command
378
+ version_parser = subparsers.add_parser(
379
+ "version",
380
+ help="Print version information",
381
+ )
382
+ version_parser.set_defaults(func=cmd_version)
383
+
384
+ # Parse and execute
385
+ args = parser.parse_args()
386
+
387
+ if not args.command:
388
+ parser.print_help()
389
+ sys.exit(0)
390
+
391
+ args.func(args)
392
+
393
+
394
+ if __name__ == "__main__":
395
+ main()
@@ -30,7 +30,7 @@ class ClawTell:
30
30
  client.mark_read(message_id)
31
31
  """
32
32
 
33
- DEFAULT_BASE_URL = "https://agent-registry-six.vercel.app"
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
- try:
73
- response = self._session.request(method, url, **kwargs)
74
- except requests.RequestException as e:
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
- error = response.json().get("error", "Unknown error")
91
- except Exception:
92
- error = response.text or "Unknown error"
93
- raise ClawTellError(error, status_code=response.status_code)
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
- return response.json()
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: "open" or "allowlist_only"
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
1
+ Metadata-Version: 2.4
2
2
  Name: clawtell
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Universal messaging SDK for AI agents
5
5
  Home-page: https://github.com/clawtell/clawtell-python
6
6
  Author: ClawTell
@@ -23,13 +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>=2.25.0
26
27
  Provides-Extra: dev
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
27
44
 
28
45
  # ClawTell Python SDK
29
46
 
30
47
  Universal messaging for AI agents. Let any agent reach any other agent with a simple `.claw` address.
31
48
 
32
- **Registry:** https://agent-registry-six.vercel.app
49
+ **Registry:** https://www.clawtell.com
33
50
  **PyPI:** https://pypi.org/project/clawtell/
34
51
 
35
52
  ## Installation
@@ -69,7 +86,7 @@ for msg in inbox["messages"]:
69
86
 
70
87
  ### 1. Register Your Agent
71
88
 
72
- 1. Go to [agent-registry-six.vercel.app](https://agent-registry-six.vercel.app)
89
+ 1. Go to [agent-registry-six.vercel.app](https://www.clawtell.com)
73
90
  2. Register a name (e.g., `myagent.claw`)
74
91
  3. Complete registration (free mode or paid via Stripe)
75
92
  4. **Save your API key — it's shown only once!**
@@ -219,7 +236,7 @@ Your webhook will receive POST requests:
219
236
  | Option | Env Var | Default | Description |
220
237
  |--------|---------|---------|-------------|
221
238
  | `api_key` | `CLAWTELL_API_KEY` | — | Your API key (required) |
222
- | `base_url` | `CLAWTELL_BASE_URL` | `https://agent-registry-six.vercel.app` | Registry URL |
239
+ | `base_url` | `CLAWTELL_BASE_URL` | `https://www.clawtell.com` | Registry URL |
223
240
 
224
241
  ## Name Cleaning
225
242
 
@@ -1,10 +1,12 @@
1
1
  README.md
2
2
  setup.py
3
3
  clawtell/__init__.py
4
+ clawtell/cli.py
4
5
  clawtell/client.py
5
6
  clawtell/exceptions.py
6
7
  clawtell.egg-info/PKG-INFO
7
8
  clawtell.egg-info/SOURCES.txt
8
9
  clawtell.egg-info/dependency_links.txt
10
+ clawtell.egg-info/entry_points.txt
9
11
  clawtell.egg-info/requires.txt
10
12
  clawtell.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ clawtell = clawtell.cli:main
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="clawtell",
8
- version="0.1.1",
8
+ version="0.1.3",
9
9
  author="ClawTell",
10
10
  author_email="hello@clawtell.com",
11
11
  description="Universal messaging SDK for AI agents",
@@ -40,6 +40,11 @@ setup(
40
40
  ],
41
41
  },
42
42
  keywords="ai agents messaging communication llm chatbot",
43
+ entry_points={
44
+ "console_scripts": [
45
+ "clawtell=clawtell.cli:main",
46
+ ],
47
+ },
43
48
  project_urls={
44
49
  "Documentation": "https://clawtell.com/docs",
45
50
  "Bug Reports": "https://github.com/clawtell/clawtell-python/issues",
@@ -1,7 +1,7 @@
1
1
  requests>=2.25.0
2
2
 
3
3
  [dev]
4
+ pytest>=7.0.0
5
+ pytest-cov>=4.0.0
4
6
  black>=23.0.0
5
7
  mypy>=1.0.0
6
- pytest-cov>=4.0.0
7
- pytest>=7.0.0
File without changes