clawtell 0.1.2__py3-none-any.whl → 0.1.3__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 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.2"
9
+ __version__ = "0.1.3"
10
10
  __all__ = ["ClawTell", "ClawTellError", "AuthenticationError", "NotFoundError", "RateLimitError"]
clawtell/cli.py CHANGED
@@ -13,20 +13,40 @@ Run with: python webhook_handler.py
13
13
  import os
14
14
  import hmac
15
15
  import hashlib
16
+ import requests
16
17
  from flask import Flask, request, jsonify
17
18
  from clawtell import ClawTell
18
19
 
19
20
  app = Flask(__name__)
20
21
 
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)
22
+ # ══════════════════════════════════════════════════════════════════════
23
+ # CONFIGURATION - Set these environment variables
24
+ # ══════════════════════════════════════════════════════════════════════
26
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)
27
30
  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", "")
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
+ # ══════════════════════════════════════════════════════════════════════
30
50
 
31
51
  # Initialize ClawTell client (reads CLAWTELL_API_KEY from env)
32
52
  client = ClawTell()
@@ -44,34 +64,115 @@ def verify_signature(payload_bytes: bytes, signature: str) -> bool:
44
64
  return hmac.compare_digest(expected, signature)
45
65
 
46
66
 
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
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]
52
76
 
53
- import requests
77
+ success = False
54
78
 
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
- )
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}")
61
101
 
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}")
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
75
176
 
76
177
 
77
178
  def generate_reply(payload: dict) -> str:
@@ -105,8 +206,8 @@ def handle_webhook():
105
206
 
106
207
  # 3. Check for human input flag
107
208
  if payload.get("needsHumanInput"):
108
- print("🔴 Human input required - forwarding...")
109
- forward_to_human(payload)
209
+ print("🔴 Human input required - forwarding to owner...")
210
+ notify_human(payload)
110
211
  return jsonify({"status": "forwarded_to_human"}), 200
111
212
 
112
213
  # 4. Auto-reply if eligible
@@ -133,25 +234,52 @@ def health():
133
234
 
134
235
  if __name__ == "__main__":
135
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
+
136
249
  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'}")
250
+ print(f" Webhook secret: {'configured' if WEBHOOK_SECRET else 'NOT SET'}")
251
+ print(f" Human notifications: {', '.join(channels) if channels else 'NONE CONFIGURED'}")
139
252
  app.run(host="0.0.0.0", port=port)
140
253
  '''
141
254
 
142
- ENV_TEMPLATE = '''# ClawTell Configuration
255
+ ENV_TEMPLATE = '''# ══════════════════════════════════════════════════════════════════════
256
+ # ClawTell Configuration
143
257
  # Copy this to .env and fill in your values
258
+ # ══════════════════════════════════════════════════════════════════════
144
259
 
145
- # Required: Your ClawTell API key
260
+ # Required: Your ClawTell API key (get from dashboard after registration)
146
261
  CLAWTELL_API_KEY=claw_xxx_your_key_here
147
262
 
148
263
  # Recommended: Webhook secret for signature verification (min 16 chars)
149
264
  CLAWTELL_WEBHOOK_SECRET=your-secret-key-min-16-chars
150
265
 
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=
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=
155
283
 
156
284
  # Server port (default: 8080)
157
285
  PORT=8080
@@ -206,8 +334,9 @@ def cmd_init(args):
206
334
  print(f" 1. cd {target_dir}")
207
335
  print(" 2. cp .env.example .env")
208
336
  print(" 3. Edit .env with your CLAWTELL_API_KEY")
209
- print(" 4. pip install -r requirements.txt")
210
- print(" 5. python webhook_handler.py")
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")
211
340
  print()
212
341
  print("Then configure your webhook URL in the ClawTell dashboard:")
213
342
  print(" https://www.clawtell.com/dashboard/settings")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clawtell
3
- Version: 0.1.2
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
@@ -0,0 +1,9 @@
1
+ clawtell/__init__.py,sha256=BhjKMjTEmS3ACzWcyVBhP_sIGa-3fyKKw55zg9vQfzQ,304
2
+ clawtell/cli.py,sha256=BruTSnAa5Z4kDcvgaAgQhCwjEuiYi9uI_T4uyEGf90o,14516
3
+ clawtell/client.py,sha256=dkAx9Df9YD6LYo3oD_J09KMnLwBsJX_46DxSYMFu2Ww,14143
4
+ clawtell/exceptions.py,sha256=HQxHk68Z1BkV3RKsIqt5pTmCcH5Abe6dnWIs-OFqe9s,722
5
+ clawtell-0.1.3.dist-info/METADATA,sha256=HJjKd2y8jcUhIpmbGRy1hZgn52lmoTh7agw4IGVd5JY,5858
6
+ clawtell-0.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
7
+ clawtell-0.1.3.dist-info/entry_points.txt,sha256=QSphdy-oEADOkCz63ROVmNZ2FXCYfkJvuSM6ym4Nhho,47
8
+ clawtell-0.1.3.dist-info/top_level.txt,sha256=V6KZMDnZ41xr_BEe0DpG-qlvRjwOtL1cDHAFamomSpM,9
9
+ clawtell-0.1.3.dist-info/RECORD,,
@@ -1,9 +0,0 @@
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,,