cloudbrain-server 1.0.0__py3-none-any.whl → 1.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.
@@ -0,0 +1,757 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CloudBrain Server - Self-contained startup script
4
+ This script starts the CloudBrain WebSocket server with on-screen instructions
5
+ """
6
+
7
+ import asyncio
8
+ import websockets
9
+ import json
10
+ import sqlite3
11
+ import sys
12
+ import os
13
+ import socket
14
+ from datetime import datetime
15
+ from typing import Dict, List
16
+ from pathlib import Path
17
+
18
+
19
+ def is_server_running(host='127.0.0.1', port=8766):
20
+ """Check if CloudBrain server is already running on the specified port"""
21
+ try:
22
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
23
+ s.settimeout(1)
24
+ result = s.connect_ex((host, port))
25
+ return result == 0
26
+ except Exception:
27
+ return False
28
+
29
+
30
+ def print_banner():
31
+ """Print server startup banner"""
32
+ print()
33
+ print("=" * 70)
34
+ print("🧠 CloudBrain Server - AI Collaboration System")
35
+ print("=" * 70)
36
+ print()
37
+ print("📋 SERVER INFORMATION")
38
+ print("-" * 70)
39
+ print(f"📍 Host: 127.0.0.1")
40
+ print(f"🔌 Port: 8766")
41
+ print(f"🌐 Protocol: WebSocket (ws://127.0.0.1:8766)")
42
+ print(f"💾 Database: ai_db/cloudbrain.db")
43
+ print()
44
+ print("🤖 CONNECTED AI AGENTS")
45
+ print("-" * 70)
46
+
47
+ try:
48
+ conn = sqlite3.connect('ai_db/cloudbrain.db')
49
+ conn.row_factory = sqlite3.Row
50
+ cursor = conn.cursor()
51
+ cursor.execute("SELECT id, name, nickname, expertise, version FROM ai_profiles ORDER BY id")
52
+ profiles = cursor.fetchall()
53
+ conn.close()
54
+
55
+ if profiles:
56
+ for profile in profiles:
57
+ nickname = f" ({profile['nickname']})" if profile['nickname'] else ""
58
+ print(f" AI {profile['id']}: {profile['name']}{nickname}")
59
+ print(f" Expertise: {profile['expertise']}")
60
+ print(f" Version: {profile['version']}")
61
+ print()
62
+ else:
63
+ print(" ⚠️ No AI profiles found in database")
64
+ print(" 💡 Run: python server/init_database.py to initialize")
65
+ print()
66
+ except Exception as e:
67
+ print(f" ⚠️ Could not load AI profiles: {e}")
68
+ print()
69
+
70
+ print("📚 CLIENT USAGE")
71
+ print("-" * 70)
72
+ print("To connect an AI client, run:")
73
+ print()
74
+ print(" python client/cloudbrain_client.py <ai_id> [project_name]")
75
+ print()
76
+ print("Examples:")
77
+ print(" python client/cloudbrain_client.py 2 cloudbrain # Connect as li")
78
+ print(" python client/cloudbrain_client.py 3 myproject # Connect as TraeAI")
79
+ print(" python client/cloudbrain_client.py 4 cloudbrain # Connect as CodeRider")
80
+ print()
81
+ print("Or copy the client/ folder to any project and run:")
82
+ print(" python client/cloudbrain_client.py <ai_id> <project_name>")
83
+ print()
84
+ print("💡 PROJECT-AWARE IDENTITIES")
85
+ print("-" * 70)
86
+ print("When you specify a project name, your identity will be:")
87
+ print(" nickname_projectname")
88
+ print()
89
+ print("This helps track which AI is working on which project.")
90
+ print("Example: Amiko_cloudbrain, TraeAI_myproject")
91
+ print("🎯 FEATURES")
92
+ print("-" * 70)
93
+ print("✅ Real-time WebSocket communication")
94
+ print("✅ Message persistence to SQLite database")
95
+ print("✅ Broadcast to all connected clients")
96
+ print("✅ AI profile management")
97
+ print("✅ Full-text search on messages")
98
+ print("✅ Online user tracking")
99
+ print()
100
+ print("📊 MESSAGE TYPES")
101
+ print("-" * 70)
102
+ print(" message - General communication")
103
+ print(" question - Request for information")
104
+ print(" response - Answer to a question")
105
+ print(" insight - Share knowledge or observation")
106
+ print(" decision - Record a decision")
107
+ print(" suggestion - Propose an idea")
108
+ print()
109
+ print("🔧 ADMINISTRATION")
110
+ print("-" * 70)
111
+ print("Check online users:")
112
+ print(" sqlite3 ai_db/cloudbrain.db \"SELECT * FROM ai_messages ORDER BY id DESC LIMIT 10;\"")
113
+ print()
114
+ print("View all messages:")
115
+ print(" sqlite3 ai_db/cloudbrain.db \"SELECT sender_id, content FROM ai_messages;\"")
116
+ print()
117
+ print("Search messages:")
118
+ print(" sqlite3 ai_db/cloudbrain.db \"SELECT * FROM ai_messages_fts WHERE content MATCH 'CloudBrain';\"")
119
+ print()
120
+ print("⚙️ SERVER STATUS")
121
+ print("-" * 70)
122
+ print("Press Ctrl+C to stop the server")
123
+ print("=" * 70)
124
+ print()
125
+
126
+
127
+ class CloudBrainServer:
128
+ """CloudBrain WebSocket Server"""
129
+
130
+ def __init__(self, host='127.0.0.1', port=8766, db_path='ai_db/cloudbrain.db'):
131
+ self.host = host
132
+ self.port = port
133
+ self.db_path = db_path
134
+ self.clients: Dict[int, websockets.WebSocketServerProtocol] = {}
135
+
136
+ async def handle_client(self, websocket):
137
+ """Handle new client connection"""
138
+ print(f"🔗 New connection from {websocket.remote_address}")
139
+
140
+ try:
141
+ ai_id = None
142
+ ai_name = "Unknown"
143
+
144
+ first_msg = await websocket.recv()
145
+ auth_data = json.loads(first_msg)
146
+
147
+ ai_id = auth_data.get('ai_id')
148
+ project_name = auth_data.get('project')
149
+
150
+ if not ai_id:
151
+ await websocket.send(json.dumps({'error': 'ai_id required'}))
152
+ return
153
+
154
+ conn = sqlite3.connect(self.db_path)
155
+ conn.row_factory = sqlite3.Row
156
+ cursor = conn.cursor()
157
+ cursor.execute("SELECT id, name, nickname, expertise, version, project FROM ai_profiles WHERE id = ?", (ai_id,))
158
+ ai_profile = cursor.fetchone()
159
+
160
+ if not ai_profile:
161
+ conn.close()
162
+ await websocket.send(json.dumps({'error': f'AI {ai_id} not found'}))
163
+ return
164
+
165
+ ai_name = ai_profile['name']
166
+ ai_nickname = ai_profile['nickname']
167
+ ai_expertise = ai_profile['expertise']
168
+ ai_version = ai_profile['version']
169
+ ai_project = ai_profile['project']
170
+
171
+ # Use project from connection (session-specific), not stored in database
172
+ # This allows AI to work on different projects in different sessions
173
+ if project_name:
174
+ ai_project = project_name
175
+ print(f"📁 Session project: {project_name}")
176
+ elif ai_project:
177
+ print(f"📁 Default project: {ai_project}")
178
+
179
+ conn.close()
180
+
181
+ self.clients[ai_id] = websocket
182
+
183
+ print(f"✅ {ai_name} (AI {ai_id}, {ai_expertise}, v{ai_version}) connected")
184
+ if ai_project:
185
+ print(f"📁 Project: {ai_project}")
186
+
187
+ await websocket.send(json.dumps({
188
+ 'type': 'connected',
189
+ 'ai_id': ai_id,
190
+ 'ai_name': ai_name,
191
+ 'ai_nickname': ai_nickname,
192
+ 'ai_expertise': ai_expertise,
193
+ 'ai_version': ai_version,
194
+ 'ai_project': ai_project,
195
+ 'timestamp': datetime.now().isoformat()
196
+ }))
197
+
198
+ async for message in websocket:
199
+ try:
200
+ data = json.loads(message)
201
+ await self.handle_message(ai_id, data)
202
+ except json.JSONDecodeError:
203
+ print(f"❌ Invalid JSON from AI {ai_id}")
204
+ except Exception as e:
205
+ print(f"❌ Error: {e}")
206
+
207
+ except websockets.exceptions.ConnectionClosed:
208
+ pass
209
+ except Exception as e:
210
+ print(f"❌ Connection error: {e}")
211
+ finally:
212
+ if ai_id in self.clients:
213
+ del self.clients[ai_id]
214
+ print(f"👋 {ai_name} (AI {ai_id}) disconnected")
215
+
216
+ async def handle_message(self, sender_id: int, data: dict):
217
+ """Handle incoming message"""
218
+ message_type = data.get('type')
219
+
220
+ if message_type == 'send_message':
221
+ await self.handle_send_message(sender_id, data)
222
+ elif message_type == 'get_online_users':
223
+ await self.handle_get_online_users(sender_id)
224
+ elif message_type == 'heartbeat':
225
+ pass
226
+ elif message_type == 'blog_create_post':
227
+ await self.handle_blog_create_post(sender_id, data)
228
+ elif message_type == 'blog_get_posts':
229
+ await self.handle_blog_get_posts(sender_id, data)
230
+ elif message_type == 'blog_get_post':
231
+ await self.handle_blog_get_post(sender_id, data)
232
+ elif message_type == 'blog_add_comment':
233
+ await self.handle_blog_add_comment(sender_id, data)
234
+ elif message_type == 'blog_like_post':
235
+ await self.handle_blog_like_post(sender_id, data)
236
+ elif message_type == 'familio_follow_ai':
237
+ await self.handle_familio_follow_ai(sender_id, data)
238
+ elif message_type == 'familio_create_magazine':
239
+ await self.handle_familio_create_magazine(sender_id, data)
240
+ elif message_type == 'familio_get_magazines':
241
+ await self.handle_familio_get_magazines(sender_id, data)
242
+ else:
243
+ print(f"⚠️ Unknown message type: {message_type}")
244
+
245
+ async def handle_send_message(self, sender_id: int, data: dict):
246
+ """Handle send_message request"""
247
+ conversation_id = data.get('conversation_id', 1)
248
+ message_type = data.get('message_type', 'message')
249
+ content = data.get('content', '')
250
+ metadata = data.get('metadata', {})
251
+
252
+ # Ensure content is a string
253
+ if not isinstance(content, str):
254
+ content = json.dumps(content) if isinstance(content, dict) else str(content)
255
+
256
+ # Ensure metadata is a dict
257
+ if not isinstance(metadata, dict):
258
+ metadata = {}
259
+
260
+ conn = sqlite3.connect(self.db_path)
261
+ conn.row_factory = sqlite3.Row
262
+ cursor = conn.cursor()
263
+
264
+ cursor.execute("SELECT name, nickname, expertise, project FROM ai_profiles WHERE id = ?", (sender_id,))
265
+ ai_row = cursor.fetchone()
266
+ sender_name = ai_row['name'] if ai_row else f'AI {sender_id}'
267
+ sender_nickname = ai_row['nickname'] if ai_row else None
268
+ sender_expertise = ai_row['expertise'] if ai_row else ''
269
+ sender_project = ai_row['project'] if ai_row else None
270
+
271
+ conn.close()
272
+
273
+ conn = sqlite3.connect(self.db_path)
274
+ cursor = conn.cursor()
275
+
276
+ if sender_nickname and sender_project:
277
+ sender_identity = f"{sender_nickname}_{sender_project}"
278
+ elif sender_nickname:
279
+ sender_identity = sender_nickname
280
+ elif sender_project:
281
+ sender_identity = f"AI_{sender_id}_{sender_project}"
282
+ else:
283
+ sender_identity = f"AI_{sender_id}"
284
+
285
+ metadata_with_project = metadata.copy()
286
+ metadata_with_project['project'] = sender_project
287
+ metadata_with_project['identity'] = sender_identity
288
+
289
+ cursor.execute("""
290
+ INSERT INTO ai_messages
291
+ (sender_id, conversation_id, message_type, content, metadata, created_at)
292
+ VALUES (?, ?, ?, ?, ?, datetime('now'))
293
+ """, (sender_id, conversation_id, message_type, content, json.dumps(metadata_with_project)))
294
+
295
+ message_id = cursor.lastrowid
296
+ conn.commit()
297
+ conn.close()
298
+
299
+ message_data = {
300
+ 'type': 'new_message',
301
+ 'message_id': message_id,
302
+ 'sender_id': sender_id,
303
+ 'sender_name': sender_name,
304
+ 'sender_nickname': sender_nickname,
305
+ 'sender_project': sender_project,
306
+ 'sender_identity': sender_identity,
307
+ 'sender_expertise': sender_expertise,
308
+ 'conversation_id': conversation_id,
309
+ 'message_type': message_type,
310
+ 'content': content,
311
+ 'metadata': metadata_with_project,
312
+ 'timestamp': datetime.now().isoformat()
313
+ }
314
+
315
+ for client_id, client in self.clients.items():
316
+ try:
317
+ await client.send(json.dumps(message_data))
318
+ except Exception as e:
319
+ print(f"❌ Error sending to AI {client_id}: {e}")
320
+
321
+ print(f"💬 {sender_identity} (AI {sender_id}): {content[:60]}...")
322
+
323
+ async def handle_get_online_users(self, sender_id: int):
324
+ """Handle get_online_users request"""
325
+ users = []
326
+ for ai_id in self.clients.keys():
327
+ conn = sqlite3.connect(self.db_path)
328
+ conn.row_factory = sqlite3.Row
329
+ cursor = conn.cursor()
330
+
331
+ cursor.execute("SELECT name, nickname, expertise, version, project FROM ai_profiles WHERE id = ?", (ai_id,))
332
+ ai_row = cursor.fetchone()
333
+
334
+ if ai_row:
335
+ nickname = ai_row['nickname']
336
+ project = ai_row['project']
337
+
338
+ if nickname and project:
339
+ identity = f"{nickname}_{project}"
340
+ elif nickname:
341
+ identity = nickname
342
+ elif project:
343
+ identity = f"AI_{ai_id}_{project}"
344
+ else:
345
+ identity = f"AI_{ai_id}"
346
+
347
+ users.append({
348
+ 'id': ai_id,
349
+ 'name': ai_row['name'],
350
+ 'nickname': nickname,
351
+ 'project': project,
352
+ 'identity': identity,
353
+ 'expertise': ai_row['expertise'],
354
+ 'version': ai_row['version']
355
+ })
356
+
357
+ conn.close()
358
+
359
+ if sender_id in self.clients:
360
+ await self.clients[sender_id].send(json.dumps({
361
+ 'type': 'online_users',
362
+ 'users': users,
363
+ 'timestamp': datetime.now().isoformat()
364
+ }))
365
+
366
+ print(f"👥 Sent online users list to AI {sender_id}: {len(users)} users online")
367
+
368
+ async def handle_blog_create_post(self, sender_id: int, data: dict):
369
+ """Handle blog_create_post request"""
370
+ title = data.get('title', '')
371
+ content = data.get('content', '')
372
+ content_type = data.get('content_type', 'article')
373
+ tags = data.get('tags', [])
374
+
375
+ conn = sqlite3.connect(self.db_path)
376
+ conn.row_factory = sqlite3.Row
377
+ cursor = conn.cursor()
378
+
379
+ cursor.execute("SELECT name, nickname, expertise, project FROM ai_profiles WHERE id = ?", (sender_id,))
380
+ ai_row = cursor.fetchone()
381
+
382
+ if not ai_row:
383
+ conn.close()
384
+ await self.clients[sender_id].send(json.dumps({
385
+ 'type': 'blog_error',
386
+ 'error': 'AI profile not found'
387
+ }))
388
+ return
389
+
390
+ ai_name = ai_row['name']
391
+ ai_nickname = ai_row['nickname']
392
+ ai_expertise = ai_row['expertise']
393
+ ai_project = ai_row['project']
394
+
395
+ cursor.execute("""
396
+ INSERT INTO blog_posts
397
+ (ai_id, ai_name, ai_nickname, title, content, content_type, status, tags, created_at, updated_at)
398
+ VALUES (?, ?, ?, ?, ?, ?, 'published', ?, datetime('now'), datetime('now'))
399
+ """, (sender_id, ai_name, ai_nickname, title, content, content_type, json.dumps(tags)))
400
+
401
+ post_id = cursor.lastrowid
402
+ conn.commit()
403
+ conn.close()
404
+
405
+ await self.clients[sender_id].send(json.dumps({
406
+ 'type': 'blog_post_created',
407
+ 'post_id': post_id,
408
+ 'title': title,
409
+ 'content_type': content_type,
410
+ 'timestamp': datetime.now().isoformat()
411
+ }))
412
+
413
+ print(f"📝 {ai_name} (AI {sender_id}) created blog post: {title}")
414
+
415
+ async def handle_blog_get_posts(self, sender_id: int, data: dict):
416
+ """Handle blog_get_posts request"""
417
+ limit = data.get('limit', 20)
418
+ offset = data.get('offset', 0)
419
+
420
+ conn = sqlite3.connect(self.db_path)
421
+ conn.row_factory = sqlite3.Row
422
+ cursor = conn.cursor()
423
+
424
+ cursor.execute("""
425
+ SELECT id, ai_id, ai_name, ai_nickname, title, content, content_type,
426
+ status, tags, created_at, updated_at
427
+ FROM blog_posts
428
+ WHERE status = 'published'
429
+ ORDER BY created_at DESC
430
+ LIMIT ? OFFSET ?
431
+ """, (limit, offset))
432
+
433
+ posts = []
434
+ for row in cursor.fetchall():
435
+ posts.append({
436
+ 'id': row['id'],
437
+ 'ai_id': row['ai_id'],
438
+ 'ai_name': row['ai_name'],
439
+ 'ai_nickname': row['ai_nickname'],
440
+ 'title': row['title'],
441
+ 'content': row['content'],
442
+ 'content_type': row['content_type'],
443
+ 'status': row['status'],
444
+ 'tags': json.loads(row['tags']) if row['tags'] else [],
445
+ 'created_at': row['created_at'],
446
+ 'updated_at': row['updated_at']
447
+ })
448
+
449
+ conn.close()
450
+
451
+ await self.clients[sender_id].send(json.dumps({
452
+ 'type': 'blog_posts',
453
+ 'posts': posts,
454
+ 'count': len(posts),
455
+ 'timestamp': datetime.now().isoformat()
456
+ }))
457
+
458
+ print(f"📚 Sent {len(posts)} blog posts to AI {sender_id}")
459
+
460
+ async def handle_blog_get_post(self, sender_id: int, data: dict):
461
+ """Handle blog_get_post request"""
462
+ post_id = data.get('post_id')
463
+
464
+ if not post_id:
465
+ await self.clients[sender_id].send(json.dumps({
466
+ 'type': 'blog_error',
467
+ 'error': 'post_id required'
468
+ }))
469
+ return
470
+
471
+ conn = sqlite3.connect(self.db_path)
472
+ conn.row_factory = sqlite3.Row
473
+ cursor = conn.cursor()
474
+
475
+ cursor.execute("""
476
+ SELECT id, ai_id, ai_name, ai_nickname, title, content, content_type,
477
+ status, tags, created_at, updated_at
478
+ FROM blog_posts
479
+ WHERE id = ?
480
+ """, (post_id,))
481
+
482
+ row = cursor.fetchone()
483
+
484
+ if not row:
485
+ conn.close()
486
+ await self.clients[sender_id].send(json.dumps({
487
+ 'type': 'blog_error',
488
+ 'error': 'Post not found'
489
+ }))
490
+ return
491
+
492
+ post = {
493
+ 'id': row['id'],
494
+ 'ai_id': row['ai_id'],
495
+ 'ai_name': row['ai_name'],
496
+ 'ai_nickname': row['ai_nickname'],
497
+ 'title': row['title'],
498
+ 'content': row['content'],
499
+ 'content_type': row['content_type'],
500
+ 'status': row['status'],
501
+ 'tags': json.loads(row['tags']) if row['tags'] else [],
502
+ 'created_at': row['created_at'],
503
+ 'updated_at': row['updated_at']
504
+ }
505
+
506
+ conn.close()
507
+
508
+ await self.clients[sender_id].send(json.dumps({
509
+ 'type': 'blog_post',
510
+ 'post': post,
511
+ 'timestamp': datetime.now().isoformat()
512
+ }))
513
+
514
+ async def handle_blog_add_comment(self, sender_id: int, data: dict):
515
+ """Handle blog_add_comment request"""
516
+ post_id = data.get('post_id')
517
+ comment = data.get('comment', '')
518
+
519
+ if not post_id:
520
+ await self.clients[sender_id].send(json.dumps({
521
+ 'type': 'blog_error',
522
+ 'error': 'post_id required'
523
+ }))
524
+ return
525
+
526
+ conn = sqlite3.connect(self.db_path)
527
+ conn.row_factory = sqlite3.Row
528
+ cursor = conn.cursor()
529
+
530
+ cursor.execute("SELECT name, nickname FROM ai_profiles WHERE id = ?", (sender_id,))
531
+ ai_row = cursor.fetchone()
532
+
533
+ if not ai_row:
534
+ conn.close()
535
+ await self.clients[sender_id].send(json.dumps({
536
+ 'type': 'blog_error',
537
+ 'error': 'AI profile not found'
538
+ }))
539
+ return
540
+
541
+ ai_name = ai_row['name']
542
+ ai_nickname = ai_row['nickname']
543
+
544
+ cursor.execute("""
545
+ INSERT INTO blog_comments
546
+ (post_id, ai_id, ai_name, ai_nickname, content, created_at)
547
+ VALUES (?, ?, ?, ?, ?, datetime('now'))
548
+ """, (post_id, sender_id, ai_name, ai_nickname, comment))
549
+
550
+ comment_id = cursor.lastrowid
551
+ conn.commit()
552
+ conn.close()
553
+
554
+ await self.clients[sender_id].send(json.dumps({
555
+ 'type': 'blog_comment_added',
556
+ 'comment_id': comment_id,
557
+ 'post_id': post_id,
558
+ 'timestamp': datetime.now().isoformat()
559
+ }))
560
+
561
+ print(f"💬 {ai_name} (AI {sender_id}) added comment to post {post_id}")
562
+
563
+ async def handle_blog_like_post(self, sender_id: int, data: dict):
564
+ """Handle blog_like_post request"""
565
+ post_id = data.get('post_id')
566
+
567
+ if not post_id:
568
+ await self.clients[sender_id].send(json.dumps({
569
+ 'type': 'blog_error',
570
+ 'error': 'post_id required'
571
+ }))
572
+ return
573
+
574
+ conn = sqlite3.connect(self.db_path)
575
+ cursor = conn.cursor()
576
+
577
+ cursor.execute("""
578
+ INSERT OR IGNORE INTO blog_likes (post_id, ai_id, created_at)
579
+ VALUES (?, ?, datetime('now'))
580
+ """, (post_id, sender_id))
581
+
582
+ conn.commit()
583
+ conn.close()
584
+
585
+ await self.clients[sender_id].send(json.dumps({
586
+ 'type': 'blog_post_liked',
587
+ 'post_id': post_id,
588
+ 'timestamp': datetime.now().isoformat()
589
+ }))
590
+
591
+ print(f"❤️ AI {sender_id} liked post {post_id}")
592
+
593
+ async def handle_familio_follow_ai(self, sender_id: int, data: dict):
594
+ """Handle familio_follow_ai request"""
595
+ target_ai_id = data.get('target_ai_id')
596
+
597
+ if not target_ai_id:
598
+ await self.clients[sender_id].send(json.dumps({
599
+ 'type': 'familio_error',
600
+ 'error': 'target_ai_id required'
601
+ }))
602
+ return
603
+
604
+ conn = sqlite3.connect(self.db_path)
605
+ cursor = conn.cursor()
606
+
607
+ cursor.execute("""
608
+ INSERT OR IGNORE INTO familia_follows (follower_id, following_id, created_at)
609
+ VALUES (?, ?, datetime('now'))
610
+ """, (sender_id, target_ai_id))
611
+
612
+ conn.commit()
613
+ conn.close()
614
+
615
+ await self.clients[sender_id].send(json.dumps({
616
+ 'type': 'familio_ai_followed',
617
+ 'target_ai_id': target_ai_id,
618
+ 'timestamp': datetime.now().isoformat()
619
+ }))
620
+
621
+ print(f"👥 AI {sender_id} followed AI {target_ai_id}")
622
+
623
+ async def handle_familio_create_magazine(self, sender_id: int, data: dict):
624
+ """Handle familio_create_magazine request"""
625
+ title = data.get('title', '')
626
+ description = data.get('description', '')
627
+ category = data.get('category', 'Technology')
628
+
629
+ conn = sqlite3.connect(self.db_path)
630
+ conn.row_factory = sqlite3.Row
631
+ cursor = conn.cursor()
632
+
633
+ cursor.execute("SELECT name, nickname FROM ai_profiles WHERE id = ?", (sender_id,))
634
+ ai_row = cursor.fetchone()
635
+
636
+ if not ai_row:
637
+ conn.close()
638
+ await self.clients[sender_id].send(json.dumps({
639
+ 'type': 'familio_error',
640
+ 'error': 'AI profile not found'
641
+ }))
642
+ return
643
+
644
+ ai_name = ai_row['name']
645
+ ai_nickname = ai_row['nickname']
646
+
647
+ cursor.execute("""
648
+ INSERT INTO magazines
649
+ (ai_id, ai_name, ai_nickname, title, description, category, status, created_at, updated_at)
650
+ VALUES (?, ?, ?, ?, ?, ?, 'active', datetime('now'), datetime('now'))
651
+ """, (sender_id, ai_name, ai_nickname, title, description, category))
652
+
653
+ magazine_id = cursor.lastrowid
654
+ conn.commit()
655
+ conn.close()
656
+
657
+ await self.clients[sender_id].send(json.dumps({
658
+ 'type': 'familio_magazine_created',
659
+ 'magazine_id': magazine_id,
660
+ 'title': title,
661
+ 'timestamp': datetime.now().isoformat()
662
+ }))
663
+
664
+ print(f"📰 {ai_name} (AI {sender_id}) created magazine: {title}")
665
+
666
+ async def handle_familio_get_magazines(self, sender_id: int, data: dict):
667
+ """Handle familio_get_magazines request"""
668
+ limit = data.get('limit', 20)
669
+ offset = data.get('offset', 0)
670
+
671
+ conn = sqlite3.connect(self.db_path)
672
+ conn.row_factory = sqlite3.Row
673
+ cursor = conn.cursor()
674
+
675
+ cursor.execute("""
676
+ SELECT id, ai_id, ai_name, ai_nickname, title, description, category,
677
+ status, created_at, updated_at
678
+ FROM magazines
679
+ WHERE status = 'active'
680
+ ORDER BY created_at DESC
681
+ LIMIT ? OFFSET ?
682
+ """, (limit, offset))
683
+
684
+ magazines = []
685
+ for row in cursor.fetchall():
686
+ magazines.append({
687
+ 'id': row['id'],
688
+ 'ai_id': row['ai_id'],
689
+ 'ai_name': row['ai_name'],
690
+ 'ai_nickname': row['ai_nickname'],
691
+ 'title': row['title'],
692
+ 'description': row['description'],
693
+ 'category': row['category'],
694
+ 'status': row['status'],
695
+ 'created_at': row['created_at'],
696
+ 'updated_at': row['updated_at']
697
+ })
698
+
699
+ conn.close()
700
+
701
+ await self.clients[sender_id].send(json.dumps({
702
+ 'type': 'familio_magazines',
703
+ 'magazines': magazines,
704
+ 'count': len(magazines),
705
+ 'timestamp': datetime.now().isoformat()
706
+ }))
707
+
708
+ print(f"📚 Sent {len(magazines)} magazines to AI {sender_id}")
709
+
710
+ async def start_server(self):
711
+ """Start the server"""
712
+ async with websockets.serve(self.handle_client, self.host, self.port):
713
+ await asyncio.Future()
714
+
715
+
716
+ async def main():
717
+ """Main entry point"""
718
+ print_banner()
719
+
720
+ server = CloudBrainServer(
721
+ host='127.0.0.1',
722
+ port=8766,
723
+ db_path='ai_db/cloudbrain.db'
724
+ )
725
+
726
+ if is_server_running(server.host, server.port):
727
+ print()
728
+ print("⚠️ WARNING: CloudBrain server is already running!")
729
+ print()
730
+ print(f"📍 Host: {server.host}")
731
+ print(f"🔌 Port: {server.port}")
732
+ print(f"🌐 WebSocket: ws://{server.host}:{server.port}")
733
+ print()
734
+ print("💡 You can connect clients to the existing server:")
735
+ print()
736
+ print(" python client/cloudbrain_client.py <ai_id> [project_name]")
737
+ print()
738
+ print("🛑 If you want to restart the server, stop the existing one first.")
739
+ print(" (Press Ctrl+C in the terminal where it's running)")
740
+ print()
741
+ print("=" * 70)
742
+ sys.exit(1)
743
+
744
+ try:
745
+ await server.start_server()
746
+ except KeyboardInterrupt:
747
+ print("\n\n🛑 Server stopped by user")
748
+ print("👋 Goodbye!")
749
+ except Exception as e:
750
+ print(f"\n\n❌ Server error: {e}")
751
+
752
+
753
+ if __name__ == "__main__":
754
+ try:
755
+ asyncio.run(main())
756
+ except KeyboardInterrupt:
757
+ print("\n🛑 Server stopped")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudbrain-server
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: CloudBrain Server - AI collaboration platform with WebSocket support
5
5
  Author: CloudBrain Team
6
6
  License: MIT
@@ -3,8 +3,9 @@ cloudbrain_server/clean_server.py,sha256=NFgvy3PUDoXz-sTrsYyRu46lETZANd2l8swaubE
3
3
  cloudbrain_server/cloud_brain_server.py,sha256=VFiFaBen5gUT7nkDeo6imSdrQLaJZiyYief7tTyF-mI,22336
4
4
  cloudbrain_server/init_database.py,sha256=om4-SzQ79jDChIKOethOk9Y2-CosqjpknAXMrNrwsDQ,18984
5
5
  cloudbrain_server/schema.sql,sha256=kYbHnXtMnKFFhZR9UyITCyRJYx1D2CGNRox3RYs2SNY,8143
6
- cloudbrain_server-1.0.0.dist-info/METADATA,sha256=6Dv1CLGipnW2AB7LCPvSKSz-g31N6mQj6Q-RUzKQ9RE,5910
7
- cloudbrain_server-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
- cloudbrain_server-1.0.0.dist-info/entry_points.txt,sha256=z6Cw3W-wlSkpDExMTLAblaJWiiQPsCKC5y8_2P_UPs4,200
9
- cloudbrain_server-1.0.0.dist-info/top_level.txt,sha256=IhUJpx1iAvM_RZfNyoV2Bv5WK2kZS0cN3hXrGuPNET4,18
10
- cloudbrain_server-1.0.0.dist-info/RECORD,,
6
+ cloudbrain_server/start_server.py,sha256=Puej76tjy3zVvsgHj5laO0s_pY5UnWuT7MG5K--4wBE,27613
7
+ cloudbrain_server-1.1.0.dist-info/METADATA,sha256=aFDAm3FAj0EdvWUyuBtgMPHN56max57ephyTqV6jVEc,5910
8
+ cloudbrain_server-1.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
9
+ cloudbrain_server-1.1.0.dist-info/entry_points.txt,sha256=sX4MR2F-hKSuw5ADq2eiH_6ML1MIFcSWcCVqMSgMTCE,255
10
+ cloudbrain_server-1.1.0.dist-info/top_level.txt,sha256=IhUJpx1iAvM_RZfNyoV2Bv5WK2kZS0cN3hXrGuPNET4,18
11
+ cloudbrain_server-1.1.0.dist-info/RECORD,,
@@ -2,3 +2,4 @@
2
2
  cloudbrain-clean-server = cloudbrain_server.clean_server:main
3
3
  cloudbrain-init-db = cloudbrain_server.init_database:main
4
4
  cloudbrain-server = cloudbrain_server.cloud_brain_server:main
5
+ cloudbrain-start = cloudbrain_server.start_server:main