squidbot 0.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.
- squidbot/__init__.py +5 -0
- squidbot/agent.py +263 -0
- squidbot/channels.py +271 -0
- squidbot/character.py +83 -0
- squidbot/client.py +318 -0
- squidbot/config.py +148 -0
- squidbot/daemon.py +310 -0
- squidbot/lanes.py +41 -0
- squidbot/main.py +157 -0
- squidbot/memory_db.py +706 -0
- squidbot/playwright_check.py +233 -0
- squidbot/plugins/__init__.py +47 -0
- squidbot/plugins/base.py +96 -0
- squidbot/plugins/hooks.py +416 -0
- squidbot/plugins/loader.py +248 -0
- squidbot/plugins/web3_plugin.py +407 -0
- squidbot/scheduler.py +214 -0
- squidbot/server.py +487 -0
- squidbot/session.py +609 -0
- squidbot/skills.py +141 -0
- squidbot/skills_template/reminder/SKILL.md +13 -0
- squidbot/skills_template/search/SKILL.md +11 -0
- squidbot/skills_template/summarize/SKILL.md +14 -0
- squidbot/tools/__init__.py +100 -0
- squidbot/tools/base.py +42 -0
- squidbot/tools/browser.py +311 -0
- squidbot/tools/coding.py +599 -0
- squidbot/tools/cron.py +218 -0
- squidbot/tools/memory_tool.py +152 -0
- squidbot/tools/web_search.py +50 -0
- squidbot-0.1.0.dist-info/METADATA +542 -0
- squidbot-0.1.0.dist-info/RECORD +34 -0
- squidbot-0.1.0.dist-info/WHEEL +4 -0
- squidbot-0.1.0.dist-info/entry_points.txt +4 -0
squidbot/server.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SquidBot Server
|
|
4
|
+
|
|
5
|
+
Runs the Telegram bot (optional) and exposes a local TCP port for client connections.
|
|
6
|
+
Supports multiple channels via the session/lane abstraction.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import signal
|
|
13
|
+
|
|
14
|
+
from .agent import run_agent_with_history
|
|
15
|
+
from .channels import (ChannelRouter, MessagePayload, TCPAdapter,
|
|
16
|
+
TelegramAdapter, get_channel_router)
|
|
17
|
+
from .config import (OPENAI_API_KEY, SQUID_PORT, TELEGRAM_BOT_TOKEN,
|
|
18
|
+
init_default_files, show_startup_info)
|
|
19
|
+
from .lanes import LANE_CRON, LANE_MAIN, CommandLane
|
|
20
|
+
from .playwright_check import require_playwright_or_exit
|
|
21
|
+
from .scheduler import Scheduler
|
|
22
|
+
from .session import (ChannelType, DeliveryContext, Session,
|
|
23
|
+
get_session_manager, record_inbound_session)
|
|
24
|
+
|
|
25
|
+
# Server configuration
|
|
26
|
+
SERVER_HOST = "127.0.0.1"
|
|
27
|
+
SERVER_PORT = SQUID_PORT
|
|
28
|
+
|
|
29
|
+
# Setup logging
|
|
30
|
+
logging.basicConfig(
|
|
31
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
|
32
|
+
)
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
# Connected TCP clients (for broadcasting scheduled messages)
|
|
36
|
+
connected_clients: dict[str, asyncio.StreamWriter] = {}
|
|
37
|
+
|
|
38
|
+
# Scheduler instance
|
|
39
|
+
scheduler: Scheduler | None = None
|
|
40
|
+
|
|
41
|
+
# Telegram app instance (for sending proactive messages)
|
|
42
|
+
telegram_app = None
|
|
43
|
+
|
|
44
|
+
# Server running flag
|
|
45
|
+
running = True
|
|
46
|
+
|
|
47
|
+
# Session manager
|
|
48
|
+
session_manager = get_session_manager()
|
|
49
|
+
|
|
50
|
+
# Channel router for multi-channel broadcasting
|
|
51
|
+
channel_router = get_channel_router()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ============================================================
|
|
55
|
+
# Message broadcasting
|
|
56
|
+
# ============================================================
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def broadcast_to_clients(message: str):
|
|
60
|
+
"""Send a message to all connected TCP clients via channel router."""
|
|
61
|
+
if not connected_clients:
|
|
62
|
+
logger.info(f"[Broadcast] No clients connected: {message[:50]}...")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
payload = MessagePayload(text=message)
|
|
66
|
+
for client_id in list(connected_clients.keys()):
|
|
67
|
+
context = DeliveryContext(
|
|
68
|
+
channel=ChannelType.TCP,
|
|
69
|
+
recipient_id=client_id,
|
|
70
|
+
)
|
|
71
|
+
success = await channel_router.send(context, payload)
|
|
72
|
+
if success:
|
|
73
|
+
logger.info(f"[Broadcast] Sent to {client_id}")
|
|
74
|
+
else:
|
|
75
|
+
# Remove disconnected clients
|
|
76
|
+
connected_clients.pop(client_id, None)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def send_to_telegram(message: str):
|
|
80
|
+
"""Send a message to Telegram if connected."""
|
|
81
|
+
global telegram_app, scheduler
|
|
82
|
+
|
|
83
|
+
if not telegram_app or not scheduler or not scheduler.chat_id:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
context = DeliveryContext(
|
|
87
|
+
channel=ChannelType.TELEGRAM,
|
|
88
|
+
recipient_id=str(scheduler.chat_id),
|
|
89
|
+
)
|
|
90
|
+
payload = MessagePayload(text=message)
|
|
91
|
+
|
|
92
|
+
success = await channel_router.send(context, payload)
|
|
93
|
+
if success:
|
|
94
|
+
logger.info(f"[Telegram] Sent message to chat {scheduler.chat_id}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def send_scheduled_message(message: str):
|
|
98
|
+
"""Send scheduled message to all active channels."""
|
|
99
|
+
logger.info(f"[Scheduled] {message[:100]}...")
|
|
100
|
+
|
|
101
|
+
# Broadcast to all active sessions
|
|
102
|
+
contexts = session_manager.get_active_delivery_contexts()
|
|
103
|
+
payload = MessagePayload(text=message)
|
|
104
|
+
|
|
105
|
+
if contexts:
|
|
106
|
+
results = await channel_router.broadcast(contexts, payload)
|
|
107
|
+
success_count = sum(1 for v in results.values() if v)
|
|
108
|
+
logger.info(f"[Scheduled] Broadcast to {success_count}/{len(results)} channels")
|
|
109
|
+
else:
|
|
110
|
+
# Fallback to legacy broadcast
|
|
111
|
+
await broadcast_to_clients(message)
|
|
112
|
+
await send_to_telegram(message)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ============================================================
|
|
116
|
+
# Local TCP server for client connections
|
|
117
|
+
# ============================================================
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
|
|
121
|
+
"""Handle a local client connection."""
|
|
122
|
+
addr = writer.get_extra_info("peername")
|
|
123
|
+
client_id = f"{addr[0]}:{addr[1]}"
|
|
124
|
+
logger.info(f"Client connected: {client_id}")
|
|
125
|
+
|
|
126
|
+
# Register client for broadcasts
|
|
127
|
+
connected_clients[client_id] = writer
|
|
128
|
+
|
|
129
|
+
# Get or create session for this TCP client
|
|
130
|
+
session = record_inbound_session(
|
|
131
|
+
channel=ChannelType.TCP,
|
|
132
|
+
recipient_id=client_id,
|
|
133
|
+
lane=LANE_MAIN,
|
|
134
|
+
delivery_context=DeliveryContext(
|
|
135
|
+
channel=ChannelType.TCP,
|
|
136
|
+
recipient_id=client_id,
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
while True:
|
|
142
|
+
# Read message (newline-delimited JSON)
|
|
143
|
+
data = await reader.readline()
|
|
144
|
+
if not data:
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
request = json.loads(data.decode().strip())
|
|
149
|
+
command = request.get("command", "chat")
|
|
150
|
+
message = request.get("message", "")
|
|
151
|
+
|
|
152
|
+
if command == "chat":
|
|
153
|
+
# Run agent with session history
|
|
154
|
+
response, updated_history = await run_agent_with_history(
|
|
155
|
+
message, session.history
|
|
156
|
+
)
|
|
157
|
+
session.history = updated_history
|
|
158
|
+
session_manager.update(session)
|
|
159
|
+
|
|
160
|
+
# Reload scheduler jobs in case new ones were created
|
|
161
|
+
if scheduler:
|
|
162
|
+
scheduler.reload_jobs()
|
|
163
|
+
|
|
164
|
+
reply = {"status": "ok", "response": response}
|
|
165
|
+
|
|
166
|
+
elif command == "clear":
|
|
167
|
+
session.clear_history()
|
|
168
|
+
session_manager.update(session)
|
|
169
|
+
reply = {"status": "ok", "response": "Conversation cleared."}
|
|
170
|
+
|
|
171
|
+
elif command == "ping":
|
|
172
|
+
reply = {"status": "ok", "response": "pong"}
|
|
173
|
+
|
|
174
|
+
else:
|
|
175
|
+
reply = {
|
|
176
|
+
"status": "error",
|
|
177
|
+
"response": f"Unknown command: {command}",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
except json.JSONDecodeError:
|
|
181
|
+
reply = {"status": "error", "response": "Invalid JSON"}
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.exception("Error processing client request")
|
|
184
|
+
reply = {"status": "error", "response": str(e)}
|
|
185
|
+
|
|
186
|
+
# Send response
|
|
187
|
+
response_data = json.dumps(reply) + "\n"
|
|
188
|
+
writer.write(response_data.encode())
|
|
189
|
+
await writer.drain()
|
|
190
|
+
|
|
191
|
+
except asyncio.CancelledError:
|
|
192
|
+
pass
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logger.error(f"Client error: {e}")
|
|
195
|
+
finally:
|
|
196
|
+
logger.info(f"Client disconnected: {client_id}")
|
|
197
|
+
connected_clients.pop(client_id, None)
|
|
198
|
+
writer.close()
|
|
199
|
+
await writer.wait_closed()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def run_tcp_server():
|
|
203
|
+
"""Run the local TCP server."""
|
|
204
|
+
server = await asyncio.start_server(handle_client, SERVER_HOST, SERVER_PORT)
|
|
205
|
+
logger.info(f"TCP server listening on {SERVER_HOST}:{SERVER_PORT}")
|
|
206
|
+
|
|
207
|
+
async with server:
|
|
208
|
+
await server.serve_forever()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ============================================================
|
|
212
|
+
# Telegram bot (optional)
|
|
213
|
+
# ============================================================
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
async def send_response_with_images(update, response: str):
|
|
217
|
+
"""Send response to Telegram, extracting and sending any screenshots as photos."""
|
|
218
|
+
import os
|
|
219
|
+
import re
|
|
220
|
+
|
|
221
|
+
max_length = 4096
|
|
222
|
+
|
|
223
|
+
# Find all screenshots in response - multiple patterns
|
|
224
|
+
screenshots = []
|
|
225
|
+
|
|
226
|
+
# Pattern 1: [SCREENSHOT:path]
|
|
227
|
+
pattern1 = r"\[SCREENSHOT:([^\]]+)\]"
|
|
228
|
+
screenshots.extend(re.findall(pattern1, response))
|
|
229
|
+
|
|
230
|
+
# Pattern 2: backtick-wrapped paths like `/.../squidbot_screenshot_*.png`
|
|
231
|
+
pattern2 = r"`([^`]*squidbot_screenshot_[^`]+\.png)`"
|
|
232
|
+
screenshots.extend(re.findall(pattern2, response))
|
|
233
|
+
|
|
234
|
+
# Pattern 3: plain paths /tmp/squidbot_screenshot_*.png or /var/folders/.../squidbot_screenshot_*.png
|
|
235
|
+
pattern3 = r"(/(?:tmp|var/folders)[^\s`\)]*squidbot_screenshot_[^\s`\)]+\.png)"
|
|
236
|
+
screenshots.extend(re.findall(pattern3, response))
|
|
237
|
+
|
|
238
|
+
# Pattern 4: markdown image syntax 
|
|
239
|
+
pattern4 = r"!\[[^\]]*\]\(([^)]*squidbot_screenshot_[^)]+\.png)\)"
|
|
240
|
+
screenshots.extend(re.findall(pattern4, response))
|
|
241
|
+
|
|
242
|
+
# Deduplicate
|
|
243
|
+
screenshots = list(set(screenshots))
|
|
244
|
+
|
|
245
|
+
# Remove screenshot paths from text response
|
|
246
|
+
text_response = response
|
|
247
|
+
text_response = re.sub(pattern1 + r"[^\n]*\n?", "", text_response)
|
|
248
|
+
text_response = re.sub(
|
|
249
|
+
r"Saved at:\s*\n?\s*" + pattern2 + r"\s*\n?", "", text_response
|
|
250
|
+
)
|
|
251
|
+
text_response = re.sub(pattern2, "", text_response)
|
|
252
|
+
# Remove markdown image syntax 
|
|
253
|
+
text_response = re.sub(
|
|
254
|
+
r"!\[[^\]]*\]\([^)]*squidbot_screenshot_[^)]+\.png\)\s*", "", text_response
|
|
255
|
+
)
|
|
256
|
+
text_response = text_response.strip()
|
|
257
|
+
|
|
258
|
+
# Send text response if any
|
|
259
|
+
if text_response:
|
|
260
|
+
if len(text_response) <= max_length:
|
|
261
|
+
await update.message.reply_text(text_response)
|
|
262
|
+
else:
|
|
263
|
+
for i in range(0, len(text_response), max_length):
|
|
264
|
+
chunk = text_response[i : i + max_length]
|
|
265
|
+
await update.message.reply_text(chunk)
|
|
266
|
+
|
|
267
|
+
# Send screenshots as photos
|
|
268
|
+
for screenshot_path in screenshots:
|
|
269
|
+
if os.path.exists(screenshot_path):
|
|
270
|
+
try:
|
|
271
|
+
with open(screenshot_path, "rb") as photo:
|
|
272
|
+
await update.message.reply_photo(photo=photo, caption="Screenshot")
|
|
273
|
+
# Clean up temp file
|
|
274
|
+
os.remove(screenshot_path)
|
|
275
|
+
logger.info(f"Sent screenshot: {screenshot_path}")
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.error(f"Failed to send screenshot: {e}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
async def run_telegram_bot():
|
|
281
|
+
"""Run the Telegram bot."""
|
|
282
|
+
global telegram_app
|
|
283
|
+
|
|
284
|
+
from telegram import Update
|
|
285
|
+
from telegram.ext import (Application, CommandHandler, ContextTypes,
|
|
286
|
+
MessageHandler, filters)
|
|
287
|
+
|
|
288
|
+
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
289
|
+
await update.message.reply_text(
|
|
290
|
+
"Hello! I'm SquidBot, your autonomous AI assistant.\n\n"
|
|
291
|
+
"I can:\n"
|
|
292
|
+
"- Remember things you tell me\n"
|
|
293
|
+
"- Search the web for information\n"
|
|
294
|
+
"- Browse websites\n"
|
|
295
|
+
"- Set reminders and scheduled tasks\n\n"
|
|
296
|
+
"Just send me a message!"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
async def clear_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
300
|
+
chat_id = update.effective_chat.id
|
|
301
|
+
session = session_manager.get(ChannelType.TELEGRAM, str(chat_id))
|
|
302
|
+
if session:
|
|
303
|
+
session.clear_history()
|
|
304
|
+
session_manager.update(session)
|
|
305
|
+
await update.message.reply_text("Conversation history cleared.")
|
|
306
|
+
|
|
307
|
+
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
308
|
+
chat_id = update.effective_chat.id
|
|
309
|
+
user_message = update.message.text
|
|
310
|
+
|
|
311
|
+
global scheduler
|
|
312
|
+
if scheduler:
|
|
313
|
+
scheduler.set_chat_id(chat_id)
|
|
314
|
+
|
|
315
|
+
# Get or create session for this Telegram chat
|
|
316
|
+
session = record_inbound_session(
|
|
317
|
+
channel=ChannelType.TELEGRAM,
|
|
318
|
+
recipient_id=str(chat_id),
|
|
319
|
+
lane=LANE_MAIN,
|
|
320
|
+
delivery_context=DeliveryContext(
|
|
321
|
+
channel=ChannelType.TELEGRAM,
|
|
322
|
+
recipient_id=str(chat_id),
|
|
323
|
+
thread_id=(
|
|
324
|
+
str(update.message.message_thread_id)
|
|
325
|
+
if update.message.message_thread_id
|
|
326
|
+
else None
|
|
327
|
+
),
|
|
328
|
+
),
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
await context.bot.send_chat_action(chat_id=chat_id, action="typing")
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
response, updated_history = await run_agent_with_history(
|
|
335
|
+
user_message, session.history
|
|
336
|
+
)
|
|
337
|
+
session.history = updated_history
|
|
338
|
+
session_manager.update(session)
|
|
339
|
+
|
|
340
|
+
# Check for screenshots in response
|
|
341
|
+
await send_response_with_images(update, response)
|
|
342
|
+
|
|
343
|
+
if scheduler:
|
|
344
|
+
scheduler.reload_jobs()
|
|
345
|
+
|
|
346
|
+
except Exception as e:
|
|
347
|
+
logger.exception("Error handling message")
|
|
348
|
+
await update.message.reply_text(f"Sorry, an error occurred: {str(e)}")
|
|
349
|
+
|
|
350
|
+
# Create application
|
|
351
|
+
telegram_app = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
|
352
|
+
telegram_app.add_handler(CommandHandler("start", start_command))
|
|
353
|
+
telegram_app.add_handler(CommandHandler("clear", clear_command))
|
|
354
|
+
telegram_app.add_handler(
|
|
355
|
+
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Initialize and run
|
|
359
|
+
await telegram_app.initialize()
|
|
360
|
+
await telegram_app.start()
|
|
361
|
+
|
|
362
|
+
# Register Telegram channel adapter
|
|
363
|
+
channel_router.register(TelegramAdapter(telegram_app.bot))
|
|
364
|
+
await telegram_app.updater.start_polling(allowed_updates=Update.ALL_TYPES)
|
|
365
|
+
|
|
366
|
+
# Wait until stopped
|
|
367
|
+
while running:
|
|
368
|
+
await asyncio.sleep(1)
|
|
369
|
+
|
|
370
|
+
# Cleanup
|
|
371
|
+
await telegram_app.updater.stop()
|
|
372
|
+
await telegram_app.stop()
|
|
373
|
+
await telegram_app.shutdown()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# ============================================================
|
|
377
|
+
# Main
|
|
378
|
+
# ============================================================
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
async def async_main():
|
|
382
|
+
"""Async main entry point."""
|
|
383
|
+
global running, scheduler
|
|
384
|
+
|
|
385
|
+
# Validate OpenAI key (required)
|
|
386
|
+
if not OPENAI_API_KEY:
|
|
387
|
+
logger.error("OPENAI_API_KEY not set")
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
# Register TCP channel adapter
|
|
391
|
+
def get_tcp_writer(client_id: str):
|
|
392
|
+
return connected_clients.get(client_id)
|
|
393
|
+
|
|
394
|
+
channel_router.register(TCPAdapter(get_tcp_writer))
|
|
395
|
+
|
|
396
|
+
# Setup scheduler with proper send_message callback
|
|
397
|
+
async def run_agent_for_scheduler(prompt: str) -> str:
|
|
398
|
+
response, _ = await run_agent_with_history(prompt, [])
|
|
399
|
+
return response
|
|
400
|
+
|
|
401
|
+
scheduler = Scheduler(
|
|
402
|
+
send_message=send_scheduled_message, run_agent=run_agent_for_scheduler
|
|
403
|
+
)
|
|
404
|
+
scheduler.start()
|
|
405
|
+
|
|
406
|
+
# Start tasks
|
|
407
|
+
tasks = []
|
|
408
|
+
|
|
409
|
+
# Always run TCP server
|
|
410
|
+
tasks.append(asyncio.create_task(run_tcp_server()))
|
|
411
|
+
logger.info("SquidBot server started")
|
|
412
|
+
logger.info(f" - Local client: {SERVER_HOST}:{SERVER_PORT}")
|
|
413
|
+
|
|
414
|
+
# Optionally run Telegram bot
|
|
415
|
+
if TELEGRAM_BOT_TOKEN:
|
|
416
|
+
tasks.append(asyncio.create_task(run_telegram_bot()))
|
|
417
|
+
logger.info(f" - Telegram bot: active")
|
|
418
|
+
else:
|
|
419
|
+
logger.info(f" - Telegram bot: disabled (no token)")
|
|
420
|
+
|
|
421
|
+
# Handle shutdown
|
|
422
|
+
def signal_handler():
|
|
423
|
+
global running
|
|
424
|
+
running = False
|
|
425
|
+
logger.info("Shutting down...")
|
|
426
|
+
|
|
427
|
+
loop = asyncio.get_event_loop()
|
|
428
|
+
for sig in (signal.SIGTERM, signal.SIGINT):
|
|
429
|
+
loop.add_signal_handler(sig, signal_handler)
|
|
430
|
+
|
|
431
|
+
# Wait for tasks
|
|
432
|
+
try:
|
|
433
|
+
await asyncio.gather(*tasks)
|
|
434
|
+
except asyncio.CancelledError:
|
|
435
|
+
pass
|
|
436
|
+
finally:
|
|
437
|
+
scheduler.stop()
|
|
438
|
+
logger.info("SquidBot server stopped")
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def run_server():
|
|
442
|
+
"""Run the server directly."""
|
|
443
|
+
# Show configuration and initialize defaults
|
|
444
|
+
show_startup_info()
|
|
445
|
+
init_default_files()
|
|
446
|
+
|
|
447
|
+
# Check Playwright browser is working before starting
|
|
448
|
+
# This will exit with error if browser is not installed/working
|
|
449
|
+
require_playwright_or_exit()
|
|
450
|
+
|
|
451
|
+
logger.info("Starting SquidBot server...")
|
|
452
|
+
asyncio.run(async_main())
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def main():
|
|
456
|
+
"""Main entry point with CLI support."""
|
|
457
|
+
import sys
|
|
458
|
+
|
|
459
|
+
if len(sys.argv) < 2:
|
|
460
|
+
# No arguments - run server directly
|
|
461
|
+
run_server()
|
|
462
|
+
return
|
|
463
|
+
|
|
464
|
+
command = sys.argv[1]
|
|
465
|
+
|
|
466
|
+
if command in ("start", "stop", "stopall", "restart", "status", "logs"):
|
|
467
|
+
# Delegate to daemon module
|
|
468
|
+
from .daemon import main as daemon_main
|
|
469
|
+
|
|
470
|
+
daemon_main()
|
|
471
|
+
else:
|
|
472
|
+
# Unknown command - show help
|
|
473
|
+
print("Usage: squidbot [command]")
|
|
474
|
+
print("")
|
|
475
|
+
print("Commands:")
|
|
476
|
+
print(" (none) Run server in foreground")
|
|
477
|
+
print(" start Start server as daemon")
|
|
478
|
+
print(" stop Stop the daemon")
|
|
479
|
+
print(" stopall Stop daemon and all clients")
|
|
480
|
+
print(" restart Restart the daemon")
|
|
481
|
+
print(" status Show daemon status")
|
|
482
|
+
print(" logs Show logs (use -f to follow)")
|
|
483
|
+
sys.exit(1)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
if __name__ == "__main__":
|
|
487
|
+
main()
|