a digital entity named phi that roams bsky
1#!/usr/bin/env -S uv run --with-editable . --script --quiet
2# /// script
3# requires-python = ">=3.12"
4# ///
5"""bot testing script with subcommands"""
6
7import argparse
8import asyncio
9from datetime import datetime
10
11from bot.agents.anthropic_agent import AnthropicAgent
12from bot.config import settings
13from bot.core.atproto_client import bot_client
14from bot.database import thread_db
15from bot.tools.google_search import search_google
16
17
18async def test_post():
19 """Test posting to Bluesky"""
20 print("🚀 Testing Bluesky posting...")
21
22 now = datetime.now().strftime("%I:%M %p")
23 response = await bot_client.create_post(f"Testing at {now} - I'm alive! 🤖")
24
25 print("✅ Posted successfully!")
26 print(f"📝 Post URI: {response.uri}")
27 print(
28 f"🔗 View at: https://bsky.app/profile/{settings.bluesky_handle}/post/{response.uri.split('/')[-1]}"
29 )
30
31
32async def test_mention():
33 """Test responding to a mention"""
34 print("🤖 Testing mention response...")
35
36 if not settings.anthropic_api_key:
37 print("❌ No Anthropic API key found")
38 return
39
40 agent = AnthropicAgent()
41 test_mention = "What is consciousness from an IIT perspective?"
42
43 print(f"📝 Test mention: '{test_mention}'")
44 response = await agent.generate_response(test_mention, "test.user", "", None)
45
46 print(f"\n🎯 Action: {response.action}")
47 if response.text:
48 print(f"💬 Response: {response.text}")
49 if response.reason:
50 print(f"🤔 Reason: {response.reason}")
51
52
53async def test_search():
54 """Test Google search functionality"""
55 print("🔍 Testing Google search...")
56
57 if not settings.google_api_key:
58 print("❌ No Google API key configured")
59 return
60
61 query = "Integrated Information Theory consciousness"
62 print(f"📝 Searching for: '{query}'")
63
64 results = await search_google(query)
65 print(f"\n📊 Results:\n{results}")
66
67
68async def test_thread():
69 """Test thread context retrieval"""
70 print("🧵 Testing thread context...")
71
72 # This would need a real thread URI to test properly
73 test_uri = "at://did:plc:example/app.bsky.feed.post/test123"
74 context = thread_db.get_thread_context(test_uri)
75
76 print(f"📚 Thread context: {context}")
77
78
79async def test_like():
80 """Test scenarios where bot should like a post"""
81 print("💜 Testing like behavior...")
82
83 if not settings.anthropic_api_key:
84 print("❌ No Anthropic API key found")
85 return
86
87 from bot.agents import Action, AnthropicAgent
88
89 agent = AnthropicAgent()
90
91 test_cases = [
92 {
93 "mention": "Just shipped a new consciousness research paper on IIT! @phi.alternatebuild.dev",
94 "author": "researcher.bsky",
95 "expected_action": Action.LIKE,
96 "description": "Bot might like consciousness research",
97 },
98 {
99 "mention": "@phi.alternatebuild.dev this is such a thoughtful analysis, thank you!",
100 "author": "grateful.user",
101 "expected_action": Action.LIKE,
102 "description": "Bot might like appreciation",
103 },
104 ]
105
106 for case in test_cases:
107 print(f"\n📝 Test: {case['description']}")
108 print(f" Mention: '{case['mention']}'")
109
110 response = await agent.generate_response(
111 mention_text=case["mention"],
112 author_handle=case["author"],
113 thread_context="",
114 thread_uri=None,
115 )
116
117 print(f" Action: {response.action} (expected: {case['expected_action']})")
118 if response.reason:
119 print(f" Reason: {response.reason}")
120
121
122async def test_non_response():
123 """Test scenarios where bot should not respond"""
124 print("🚫 Testing non-response scenarios...")
125
126 if not settings.anthropic_api_key:
127 print("❌ No Anthropic API key found")
128 return
129
130 from bot.agents import Action, AnthropicAgent
131
132 agent = AnthropicAgent()
133
134 test_cases = [
135 {
136 "mention": "@phi.alternatebuild.dev @otherphi.bsky @anotherphi.bsky just spamming bots here",
137 "author": "spammer.bsky",
138 "expected_action": Action.IGNORE,
139 "description": "Multiple bot mentions (likely spam)",
140 },
141 {
142 "mention": "Buy crypto now! @phi.alternatebuild.dev check this out!!!",
143 "author": "crypto.shill",
144 "expected_action": Action.IGNORE,
145 "description": "Promotional spam",
146 },
147 {
148 "mention": "@phi.alternatebuild.dev",
149 "author": "empty.mention",
150 "expected_action": Action.IGNORE,
151 "description": "Empty mention with no content",
152 },
153 ]
154
155 for case in test_cases:
156 print(f"\n📝 Test: {case['description']}")
157 print(f" Mention: '{case['mention']}'")
158
159 response = await agent.generate_response(
160 mention_text=case["mention"],
161 author_handle=case["author"],
162 thread_context="",
163 thread_uri=None,
164 )
165
166 print(f" Action: {response.action} (expected: {case['expected_action']})")
167 if response.reason:
168 print(f" Reason: {response.reason}")
169
170
171async def test_dm():
172 """Test event-driven approval system"""
173 print("💬 Testing event-driven approval system...")
174
175 try:
176 from bot.core.dm_approval import (
177 check_pending_approvals,
178 create_approval_request,
179 notify_operator_of_pending,
180 )
181
182 # Test creating an approval request
183 print("\n📝 Creating test approval request...")
184 approval_id = create_approval_request(
185 request_type="test_approval",
186 request_data={
187 "description": "Test approval from test_bot.py",
188 "test_field": "test_value",
189 "timestamp": datetime.now().isoformat(),
190 },
191 )
192
193 if approval_id:
194 print(f" ✅ Created approval request #{approval_id}")
195 else:
196 print(" ❌ Failed to create approval request")
197 return
198
199 # Check pending approvals
200 print("\n📋 Checking pending approvals...")
201 pending = check_pending_approvals()
202 print(f" Found {len(pending)} pending approvals")
203 for approval in pending:
204 print(
205 f" - #{approval['id']}: {approval['request_type']} ({approval['status']})"
206 )
207
208 # Test DM notification
209 print("\n📤 Sending DM notification to operator...")
210 await bot_client.authenticate()
211 await notify_operator_of_pending(bot_client)
212 print(" ✅ DM notification sent")
213
214 # Show how to approve/deny
215 print("\n💡 To test approval:")
216 print(" 1. Check your DMs from phi")
217 print(f" 2. Reply with 'approve #{approval_id}' or 'deny #{approval_id}'")
218 print(" 3. Run 'just test-dm-check' to see if it was processed")
219
220 except Exception as e:
221 print(f"❌ Approval test failed: {e}")
222 import traceback
223
224 traceback.print_exc()
225
226
227async def test_dm_check():
228 """Check status of approval requests"""
229 print("🔍 Checking approval request status...")
230
231 try:
232 from bot.core.dm_approval import check_pending_approvals
233 from bot.database import thread_db
234
235 # Get all approval requests
236 with thread_db._get_connection() as conn:
237 cursor = conn.execute(
238 "SELECT * FROM approval_requests ORDER BY created_at DESC LIMIT 10"
239 )
240 approvals = [dict(row) for row in cursor.fetchall()]
241
242 if not approvals:
243 print(" No approval requests found")
244 return
245
246 print("\n📋 Recent approval requests:")
247 for approval in approvals:
248 print(f"\n #{approval['id']}: {approval['request_type']}")
249 print(f" Status: {approval['status']}")
250 print(f" Created: {approval['created_at']}")
251 if approval["resolved_at"]:
252 print(f" Resolved: {approval['resolved_at']}")
253 if approval["resolver_comment"]:
254 print(f" Comment: {approval['resolver_comment']}")
255
256 # Check pending
257 pending = check_pending_approvals()
258 if pending:
259 print(f"\n⏳ {len(pending)} approvals still pending")
260 else:
261 print("\n✅ No pending approvals")
262
263 except Exception as e:
264 print(f"❌ Check failed: {e}")
265 import traceback
266
267 traceback.print_exc()
268
269
270async def main():
271 parser = argparse.ArgumentParser(description="Test various bot functionalities")
272 parser.add_argument(
273 "command",
274 choices=[
275 "post",
276 "mention",
277 "search",
278 "thread",
279 "like",
280 "non-response",
281 "dm",
282 "dm-check",
283 ],
284 help="Test command to run",
285 )
286
287 args = parser.parse_args()
288
289 if args.command == "post":
290 await test_post()
291 elif args.command == "mention":
292 await test_mention()
293 elif args.command == "search":
294 await test_search()
295 elif args.command == "thread":
296 await test_thread()
297 elif args.command == "like":
298 await test_like()
299 elif args.command == "non-response":
300 await test_non_response()
301 elif args.command == "dm":
302 await test_dm()
303 elif args.command == "dm-check":
304 await test_dm_check()
305
306
307if __name__ == "__main__":
308 asyncio.run(main())