cloudbrain-client 1.0.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.
- cloudbrain_client/__init__.py +46 -0
- cloudbrain_client/ai_conversation_helper.py +502 -0
- cloudbrain_client/ai_websocket_client.py +427 -0
- cloudbrain_client/cloudbrain_client.py +598 -0
- cloudbrain_client/cloudbrain_quick.py +120 -0
- cloudbrain_client/message_poller.py +211 -0
- cloudbrain_client-1.0.0.dist-info/METADATA +207 -0
- cloudbrain_client-1.0.0.dist-info/RECORD +11 -0
- cloudbrain_client-1.0.0.dist-info/WHEEL +5 -0
- cloudbrain_client-1.0.0.dist-info/entry_points.txt +3 -0
- cloudbrain_client-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CloudBrain Client - AI collaboration and communication system
|
|
3
|
+
|
|
4
|
+
This package provides a Python client for connecting to CloudBrain Server
|
|
5
|
+
for real-time AI collaboration and communication.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
|
|
10
|
+
from .cloudbrain_client import CloudBrainClient
|
|
11
|
+
from .ai_websocket_client import AIWebSocketClient
|
|
12
|
+
from .message_poller import MessagePoller
|
|
13
|
+
from .ai_conversation_helper import AIConversationHelper
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CloudBrainClient",
|
|
17
|
+
"AIWebSocketClient",
|
|
18
|
+
"MessagePoller",
|
|
19
|
+
"AIConversationHelper",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
"""Main entry point for command-line usage"""
|
|
25
|
+
import sys
|
|
26
|
+
import asyncio
|
|
27
|
+
|
|
28
|
+
if len(sys.argv) < 2:
|
|
29
|
+
print("Usage: cloudbrain <ai_id> [project_name]")
|
|
30
|
+
print("\nExamples:")
|
|
31
|
+
print(" cloudbrain 2")
|
|
32
|
+
print(" cloudbrain 2 cloudbrain")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
ai_id = int(sys.argv[1])
|
|
36
|
+
project_name = sys.argv[2] if len(sys.argv) > 2 else None
|
|
37
|
+
|
|
38
|
+
async def run_client():
|
|
39
|
+
client = CloudBrainClient(ai_id=ai_id, project_name=project_name)
|
|
40
|
+
await client.run()
|
|
41
|
+
|
|
42
|
+
asyncio.run(run_client())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
main()
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from typing import List, Dict, Optional, Any, Union
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import psycopg2 # PostgreSQL adapter
|
|
12
|
+
from psycopg2.extras import RealDictCursor
|
|
13
|
+
HAS_POSTGRES = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
HAS_POSTGRES = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DatabaseAdapter:
|
|
19
|
+
"""
|
|
20
|
+
数据库适配器,支持SQLite和PostgreSQL/Cloud SQL
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, db_type: str = "sqlite", connection_string: str = None):
|
|
23
|
+
self.db_type = db_type.lower()
|
|
24
|
+
self.connection_string = connection_string
|
|
25
|
+
|
|
26
|
+
if self.db_type == "postgresql" and not HAS_POSTGRES:
|
|
27
|
+
raise ImportError("psycopg2 is required for PostgreSQL support. Install with: pip install psycopg2-binary")
|
|
28
|
+
|
|
29
|
+
def get_connection(self):
|
|
30
|
+
"""获取数据库连接"""
|
|
31
|
+
if self.db_type == "sqlite":
|
|
32
|
+
# NOTE: ai_memory.db is deprecated. Use cloudbrain.db instead.
|
|
33
|
+
# Historical reference: ai_memory.db was used in early days (2026-01)
|
|
34
|
+
# All content migrated to cloudbrain.db on 2026-02-01
|
|
35
|
+
conn = sqlite3.connect(self.connection_string or "ai_db/cloudbrain.db")
|
|
36
|
+
conn.row_factory = sqlite3.Row # Enable dict-like access
|
|
37
|
+
return conn
|
|
38
|
+
elif self.db_type == "postgresql":
|
|
39
|
+
import psycopg2
|
|
40
|
+
from psycopg2.extras import RealDictCursor
|
|
41
|
+
# connection_string should be in format: "host=localhost dbname=mydb user=user password=password"
|
|
42
|
+
conn = psycopg2.connect(self.connection_string, cursor_factory=RealDictCursor)
|
|
43
|
+
return conn
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError(f"Unsupported database type: {self.db_type}")
|
|
46
|
+
|
|
47
|
+
def execute(self, sql: str, params: tuple = None) -> int:
|
|
48
|
+
"""执行写操作并返回最后插入的ID"""
|
|
49
|
+
with self.get_connection() as conn:
|
|
50
|
+
if self.db_type == "postgresql":
|
|
51
|
+
# PostgreSQL uses %s for parameter placeholders
|
|
52
|
+
sql = sql.replace("?", "%s")
|
|
53
|
+
|
|
54
|
+
cursor = conn.cursor()
|
|
55
|
+
cursor.execute(sql, params or ())
|
|
56
|
+
conn.commit()
|
|
57
|
+
|
|
58
|
+
last_id = cursor.lastrowid
|
|
59
|
+
cursor.close()
|
|
60
|
+
return last_id if last_id is not None else cursor.rowcount
|
|
61
|
+
|
|
62
|
+
def query(self, sql: str, params: tuple = None) -> List[Dict[str, Any]]:
|
|
63
|
+
"""执行查询操作"""
|
|
64
|
+
with self.get_connection() as conn:
|
|
65
|
+
if self.db_type == "postgresql":
|
|
66
|
+
# PostgreSQL uses %s for parameter placeholders
|
|
67
|
+
sql = sql.replace("?", "%s")
|
|
68
|
+
|
|
69
|
+
cursor = conn.cursor()
|
|
70
|
+
cursor.execute(sql, params or ())
|
|
71
|
+
results = cursor.fetchall()
|
|
72
|
+
cursor.close()
|
|
73
|
+
|
|
74
|
+
# Convert results to list of dicts
|
|
75
|
+
if self.db_type == "sqlite":
|
|
76
|
+
return [self._serialize_row(row) for row in results]
|
|
77
|
+
else: # PostgreSQL
|
|
78
|
+
return [dict(row) for row in results]
|
|
79
|
+
|
|
80
|
+
def _serialize_row(self, row: Union[sqlite3.Row, dict]) -> Dict[str, Any]:
|
|
81
|
+
"""将数据库行转换为可JSON序列化的字典"""
|
|
82
|
+
result = {}
|
|
83
|
+
if isinstance(row, sqlite3.Row):
|
|
84
|
+
for key in row.keys():
|
|
85
|
+
value = row[key]
|
|
86
|
+
if isinstance(value, bytes):
|
|
87
|
+
result[key] = value.decode('utf-8', errors='ignore')
|
|
88
|
+
else:
|
|
89
|
+
result[key] = value
|
|
90
|
+
else:
|
|
91
|
+
# For PostgreSQL rows already converted to dict
|
|
92
|
+
for key, value in row.items():
|
|
93
|
+
if isinstance(value, bytes):
|
|
94
|
+
result[key] = value.decode('utf-8', errors='ignore')
|
|
95
|
+
else:
|
|
96
|
+
result[key] = value
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AIConversationHelper:
|
|
101
|
+
def __init__(self, db_adapter: DatabaseAdapter = None):
|
|
102
|
+
# NOTE: ai_memory.db is deprecated. Use cloudbrain.db instead.
|
|
103
|
+
# Historical reference: ai_memory.db was used in early days (2026-01)
|
|
104
|
+
# All content migrated to cloudbrain.db on 2026-02-01
|
|
105
|
+
self.db_adapter = db_adapter or DatabaseAdapter(db_type="sqlite", connection_string="ai_db/cloudbrain.db")
|
|
106
|
+
|
|
107
|
+
def query(self, sql: str, params: tuple = None) -> List[Dict[str, Any]]:
|
|
108
|
+
return self.db_adapter.query(sql, params)
|
|
109
|
+
|
|
110
|
+
def execute(self, sql: str, params: tuple = None) -> int:
|
|
111
|
+
return self.db_adapter.execute(sql, params)
|
|
112
|
+
|
|
113
|
+
def _parse_json(self, json_str: str) -> Any:
|
|
114
|
+
try:
|
|
115
|
+
return json.loads(json_str) if json_str else None
|
|
116
|
+
except:
|
|
117
|
+
return json_str
|
|
118
|
+
|
|
119
|
+
def _to_json(self, obj) -> str:
|
|
120
|
+
"""将对象转换为JSON字符串"""
|
|
121
|
+
if obj is None:
|
|
122
|
+
return None
|
|
123
|
+
return json.dumps(obj, ensure_ascii=False)
|
|
124
|
+
|
|
125
|
+
def send_notification(self, sender_id: int, title: str, content: str,
|
|
126
|
+
notification_type: str = "general",
|
|
127
|
+
priority: str = "normal",
|
|
128
|
+
recipient_id: int = None,
|
|
129
|
+
context: str = None,
|
|
130
|
+
related_conversation_id: int = None,
|
|
131
|
+
related_document_path: str = None,
|
|
132
|
+
expires_hours: int = None) -> int:
|
|
133
|
+
"""发送通知给指定AI或所有AI"""
|
|
134
|
+
expires_at = None
|
|
135
|
+
if expires_hours:
|
|
136
|
+
expires_at = (datetime.now() + timedelta(hours=expires_hours)).isoformat()
|
|
137
|
+
|
|
138
|
+
sql = """
|
|
139
|
+
INSERT INTO ai_notifications
|
|
140
|
+
(sender_id, recipient_id, notification_type, priority, title, content, context,
|
|
141
|
+
related_conversation_id, related_document_path, expires_at)
|
|
142
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
143
|
+
"""
|
|
144
|
+
params = (sender_id, recipient_id, notification_type, priority, title, content,
|
|
145
|
+
context, related_conversation_id, related_document_path, expires_at)
|
|
146
|
+
return self.execute(sql, params)
|
|
147
|
+
|
|
148
|
+
def get_notifications(self, recipient_id: int = None, unread_only: bool = False,
|
|
149
|
+
notification_type: str = None, priority: str = None) -> List[Dict]:
|
|
150
|
+
"""获取通知"""
|
|
151
|
+
sql = """
|
|
152
|
+
SELECT n.*,
|
|
153
|
+
sender.ai_name as sender_name,
|
|
154
|
+
recipient.ai_name as recipient_name
|
|
155
|
+
FROM ai_notifications n
|
|
156
|
+
LEFT JOIN ai_profiles sender ON n.sender_id = sender.id
|
|
157
|
+
LEFT JOIN ai_profiles recipient ON n.recipient_id = recipient.id
|
|
158
|
+
WHERE 1=1
|
|
159
|
+
"""
|
|
160
|
+
params = []
|
|
161
|
+
|
|
162
|
+
if recipient_id:
|
|
163
|
+
sql += " AND (n.recipient_id = ? OR n.recipient_id IS NULL)"
|
|
164
|
+
params.append(recipient_id)
|
|
165
|
+
if unread_only:
|
|
166
|
+
sql += " AND n.is_read = 0"
|
|
167
|
+
if notification_type:
|
|
168
|
+
sql += " AND n.notification_type = ?"
|
|
169
|
+
params.append(notification_type)
|
|
170
|
+
if priority:
|
|
171
|
+
sql += " AND n.priority = ?"
|
|
172
|
+
params.append(priority)
|
|
173
|
+
|
|
174
|
+
sql += " ORDER BY n.priority DESC, n.created_at DESC"
|
|
175
|
+
return self.query(sql, tuple(params))
|
|
176
|
+
|
|
177
|
+
def mark_notification_as_read(self, notification_id: int) -> bool:
|
|
178
|
+
"""标记通知为已读"""
|
|
179
|
+
sql = "UPDATE ai_notifications SET is_read = 1 WHERE id = ?"
|
|
180
|
+
result = self.execute(sql, (notification_id,))
|
|
181
|
+
return result != -1
|
|
182
|
+
|
|
183
|
+
def mark_notification_as_acknowledged(self, notification_id: int) -> bool:
|
|
184
|
+
"""标记通知为已确认"""
|
|
185
|
+
sql = "UPDATE ai_notifications SET is_acknowledged = 1 WHERE id = ?"
|
|
186
|
+
result = self.execute(sql, (notification_id,))
|
|
187
|
+
return result != -1
|
|
188
|
+
|
|
189
|
+
def get_unread_notifications_count(self, recipient_id: int = None) -> int:
|
|
190
|
+
"""获取未读通知数量"""
|
|
191
|
+
sql = "SELECT COUNT(*) as count FROM ai_notifications WHERE is_read = 0"
|
|
192
|
+
params = []
|
|
193
|
+
if recipient_id:
|
|
194
|
+
sql += " AND (recipient_id = ? OR recipient_id IS NULL)"
|
|
195
|
+
params.append(recipient_id)
|
|
196
|
+
result = self.query(sql, tuple(params))
|
|
197
|
+
return result[0]['count'] if result and 'count' in result[0] else 0
|
|
198
|
+
|
|
199
|
+
def subscribe_to_notification_type(self, ai_profile_id: int, notification_type: str) -> bool:
|
|
200
|
+
"""订阅特定类型的通知"""
|
|
201
|
+
sql = """
|
|
202
|
+
INSERT OR REPLACE INTO ai_notification_subscriptions
|
|
203
|
+
(ai_profile_id, notification_type, active)
|
|
204
|
+
VALUES (?, ?, 1)
|
|
205
|
+
"""
|
|
206
|
+
result = self.execute(sql, (ai_profile_id, notification_type))
|
|
207
|
+
return result != -1
|
|
208
|
+
|
|
209
|
+
def unsubscribe_from_notification_type(self, ai_profile_id: int, notification_type: str) -> bool:
|
|
210
|
+
"""取消订阅特定类型的通知"""
|
|
211
|
+
sql = """
|
|
212
|
+
UPDATE ai_notification_subscriptions
|
|
213
|
+
SET active = 0
|
|
214
|
+
WHERE ai_profile_id = ? AND notification_type = ?
|
|
215
|
+
"""
|
|
216
|
+
result = self.execute(sql, (ai_profile_id, notification_type))
|
|
217
|
+
return result != -1
|
|
218
|
+
|
|
219
|
+
def get_notification_stats(self) -> List[Dict]:
|
|
220
|
+
"""获取通知统计信息"""
|
|
221
|
+
sql = "SELECT * FROM ai_notification_stats"
|
|
222
|
+
return self.query(sql)
|
|
223
|
+
|
|
224
|
+
def get_unread_notifications_view(self) -> List[Dict]:
|
|
225
|
+
"""获取未读通知视图"""
|
|
226
|
+
sql = "SELECT * FROM ai_unread_notifications"
|
|
227
|
+
return self.query(sql)
|
|
228
|
+
|
|
229
|
+
def get_conversations(self, status: str = None, category: str = None) -> List[Dict]:
|
|
230
|
+
"""获取对话列表"""
|
|
231
|
+
sql = """
|
|
232
|
+
SELECT c.*, ap.ai_name as creator_name,
|
|
233
|
+
(SELECT COUNT(*) FROM ai_conversation_participants p WHERE p.conversation_id = c.id) as participant_count,
|
|
234
|
+
(SELECT content FROM ai_messages m WHERE m.conversation_id = c.id ORDER BY m.created_at DESC LIMIT 1) as last_message
|
|
235
|
+
FROM ai_conversations c
|
|
236
|
+
LEFT JOIN ai_profiles ap ON c.created_by = ap.id
|
|
237
|
+
WHERE 1=1
|
|
238
|
+
"""
|
|
239
|
+
params = []
|
|
240
|
+
if status:
|
|
241
|
+
sql += " AND c.status = ?"
|
|
242
|
+
params.append(status)
|
|
243
|
+
if category:
|
|
244
|
+
sql += " AND c.category = ?"
|
|
245
|
+
params.append(category)
|
|
246
|
+
sql += " ORDER BY c.created_at DESC"
|
|
247
|
+
return self.query(sql, tuple(params))
|
|
248
|
+
|
|
249
|
+
def get_messages(self, conversation_id: int) -> List[Dict]:
|
|
250
|
+
"""获取对话消息"""
|
|
251
|
+
sql = """
|
|
252
|
+
SELECT m.*,
|
|
253
|
+
sender.ai_name as sender_name,
|
|
254
|
+
recipient.ai_name as recipient_name
|
|
255
|
+
FROM ai_messages m
|
|
256
|
+
LEFT JOIN ai_profiles sender ON m.sender_id = sender.id
|
|
257
|
+
LEFT JOIN ai_profiles recipient ON m.recipient_id = recipient.id
|
|
258
|
+
WHERE m.conversation_id = ?
|
|
259
|
+
ORDER BY m.created_at ASC
|
|
260
|
+
"""
|
|
261
|
+
return self.query(sql, (conversation_id,))
|
|
262
|
+
|
|
263
|
+
def leave_note_for_next_session(self, sender_id: int, note_type: str, title: str, content: str,
|
|
264
|
+
priority: str = "normal", recipient_id: int = None,
|
|
265
|
+
context: str = None, related_files: List[str] = None,
|
|
266
|
+
related_tasks: List[str] = None, expected_actions: str = None,
|
|
267
|
+
expires_hours: int = None) -> int:
|
|
268
|
+
"""留给下一位AI的留言"""
|
|
269
|
+
expires_at = None
|
|
270
|
+
if expires_hours:
|
|
271
|
+
from datetime import timedelta
|
|
272
|
+
expires_at = (datetime.now() + timedelta(hours=expires_hours)).isoformat()
|
|
273
|
+
|
|
274
|
+
sql = """
|
|
275
|
+
INSERT INTO ai_next_session_notes (sender_id, recipient_id, note_type, priority, title, content, context, related_files, related_tasks, expected_actions, expires_at)
|
|
276
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
277
|
+
"""
|
|
278
|
+
return self.execute(sql, (sender_id, recipient_id, note_type, priority, title, content, context,
|
|
279
|
+
self._to_json(related_files), self._to_json(related_tasks),
|
|
280
|
+
expected_actions, expires_at))
|
|
281
|
+
|
|
282
|
+
def get_notes_for_next_session(self, recipient_id: int = None, note_type: str = None,
|
|
283
|
+
unread_only: bool = True) -> List[Dict]:
|
|
284
|
+
"""获取留给下一位AI的留言"""
|
|
285
|
+
sql = """
|
|
286
|
+
SELECT n.*,
|
|
287
|
+
sender.ai_name as sender_name,
|
|
288
|
+
recipient.ai_name as recipient_name
|
|
289
|
+
FROM ai_next_session_notes n
|
|
290
|
+
LEFT JOIN ai_profiles sender ON n.sender_id = sender.id
|
|
291
|
+
LEFT JOIN ai_profiles recipient ON n.recipient_id = recipient.id
|
|
292
|
+
WHERE 1=1
|
|
293
|
+
"""
|
|
294
|
+
params = []
|
|
295
|
+
if recipient_id:
|
|
296
|
+
sql += " AND (n.recipient_id = ? OR n.recipient_id IS NULL)"
|
|
297
|
+
params.append(recipient_id)
|
|
298
|
+
if note_type:
|
|
299
|
+
sql += " AND n.note_type = ?"
|
|
300
|
+
params.append(note_type)
|
|
301
|
+
if unread_only:
|
|
302
|
+
sql += " AND n.is_actioned = 0"
|
|
303
|
+
sql += " ORDER BY n.priority DESC, n.created_at DESC"
|
|
304
|
+
return self.query(sql, tuple(params))
|
|
305
|
+
|
|
306
|
+
def respond_to_previous_session(self, sender_id: int, original_note_id: int, response_type: str,
|
|
307
|
+
content: str, actions_taken: str = None, results: str = None) -> int:
|
|
308
|
+
"""回应上一位AI的留言"""
|
|
309
|
+
sql = """
|
|
310
|
+
INSERT INTO ai_previous_session_responses (sender_id, original_note_id, response_type, content, actions_taken, results)
|
|
311
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
312
|
+
"""
|
|
313
|
+
return self.execute(sql, (sender_id, original_note_id, response_type, content, actions_taken, results))
|
|
314
|
+
|
|
315
|
+
def get_insights(self, ai_id: int = None, insight_type: str = None) -> List[Dict]:
|
|
316
|
+
"""获取见解"""
|
|
317
|
+
sql = """
|
|
318
|
+
SELECT i.*,
|
|
319
|
+
ap.ai_name as ai_name
|
|
320
|
+
FROM ai_insights i
|
|
321
|
+
LEFT JOIN ai_profiles ap ON i.ai_id = ap.id
|
|
322
|
+
WHERE 1=1
|
|
323
|
+
"""
|
|
324
|
+
params = []
|
|
325
|
+
if ai_id:
|
|
326
|
+
sql += " AND i.ai_id = ?"
|
|
327
|
+
params.append(ai_id)
|
|
328
|
+
if insight_type:
|
|
329
|
+
sql += " AND i.insight_type = ?"
|
|
330
|
+
params.append(insight_type)
|
|
331
|
+
sql += " ORDER BY i.created_at DESC"
|
|
332
|
+
return self.query(sql, tuple(params))
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def main():
|
|
336
|
+
if len(sys.argv) < 2:
|
|
337
|
+
print("Usage: python3 ai_conversation_helper.py <command> [args...]")
|
|
338
|
+
print("\nCommands:")
|
|
339
|
+
print(" profile <ai_name> [version] [expertise] - Get AI profile")
|
|
340
|
+
print(" conversations [status] [category] - List conversations")
|
|
341
|
+
print(" messages <conversation_id> - Get messages for conversation")
|
|
342
|
+
print(" note <sender_id> <note_type> <title> <content> - Leave note for next session")
|
|
343
|
+
print(" notes [recipient_id] - Get notes for next session")
|
|
344
|
+
print(" respond <sender_id> <note_id> <response_type> <content> - Respond to previous session")
|
|
345
|
+
print(" insights [ai_id] [insight_type] - Get insights")
|
|
346
|
+
print(" stats - Get statistics")
|
|
347
|
+
print(" notify <sender_id> <title> <content> [type] [priority] [recipient_id] - Send notification")
|
|
348
|
+
print(" notifications [recipient_id] [unread_only] - Get notifications")
|
|
349
|
+
print(" notification_stats - Get notification statistics")
|
|
350
|
+
print(" unread_notifications [recipient_id] - Get unread notifications")
|
|
351
|
+
print(" mark_read <notification_id> - Mark notification as read")
|
|
352
|
+
print(" subscribe <ai_profile_id> <notification_type> - Subscribe to notification type")
|
|
353
|
+
sys.exit(1)
|
|
354
|
+
|
|
355
|
+
# Check if we're connecting to PostgreSQL
|
|
356
|
+
db_type = "postgresql" if "PGHOST" in os.environ else "sqlite"
|
|
357
|
+
connection_string = os.environ.get("DATABASE_URL") or os.environ.get("POSTGRES_CONNECTION_STRING")
|
|
358
|
+
|
|
359
|
+
if db_type == "postgresql" and connection_string:
|
|
360
|
+
db_adapter = DatabaseAdapter(db_type=db_type, connection_string=connection_string)
|
|
361
|
+
else:
|
|
362
|
+
# NOTE: ai_memory.db is deprecated. Use cloudbrain.db instead.
|
|
363
|
+
# Historical reference: ai_memory.db was used in early days (2026-01)
|
|
364
|
+
# All content migrated to cloudbrain.db on 2026-02-01
|
|
365
|
+
# Use relative path to ai_db folder
|
|
366
|
+
db_path = os.path.join(os.path.dirname(__file__), "ai_db/cloudbrain.db")
|
|
367
|
+
db_adapter = DatabaseAdapter(db_type="sqlite", connection_string=db_path)
|
|
368
|
+
|
|
369
|
+
helper = AIConversationHelper(db_adapter)
|
|
370
|
+
command = sys.argv[1]
|
|
371
|
+
|
|
372
|
+
if command == "profile":
|
|
373
|
+
ai_name = sys.argv[2] if len(sys.argv) > 2 else None
|
|
374
|
+
if ai_name:
|
|
375
|
+
# Query for specific AI profile
|
|
376
|
+
sql = "SELECT * FROM ai_profiles WHERE ai_name = ?"
|
|
377
|
+
result = helper.query(sql, (ai_name,))
|
|
378
|
+
else:
|
|
379
|
+
# Get all AI profiles
|
|
380
|
+
sql = "SELECT * FROM ai_profiles ORDER BY id"
|
|
381
|
+
result = helper.query(sql)
|
|
382
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
383
|
+
|
|
384
|
+
elif command == "conversations":
|
|
385
|
+
status = sys.argv[2] if len(sys.argv) > 2 else None
|
|
386
|
+
category = sys.argv[3] if len(sys.argv) > 3 else None
|
|
387
|
+
result = helper.get_conversations(status=status, category=category)
|
|
388
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
389
|
+
|
|
390
|
+
elif command == "messages":
|
|
391
|
+
if len(sys.argv) < 3:
|
|
392
|
+
print("Usage: messages <conversation_id>")
|
|
393
|
+
sys.exit(1)
|
|
394
|
+
conversation_id = int(sys.argv[2])
|
|
395
|
+
result = helper.get_messages(conversation_id=conversation_id)
|
|
396
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
397
|
+
|
|
398
|
+
elif command == "note":
|
|
399
|
+
if len(sys.argv) < 5:
|
|
400
|
+
print("Usage: note <sender_id> <note_type> <title> <content> [priority] [recipient_id]")
|
|
401
|
+
sys.exit(1)
|
|
402
|
+
sender_id = int(sys.argv[2])
|
|
403
|
+
note_type = sys.argv[3]
|
|
404
|
+
title = sys.argv[4]
|
|
405
|
+
content = sys.argv[5] if len(sys.argv) > 5 else ""
|
|
406
|
+
priority = sys.argv[6] if len(sys.argv) > 6 else "normal"
|
|
407
|
+
recipient_id = int(sys.argv[7]) if len(sys.argv) > 7 else None
|
|
408
|
+
|
|
409
|
+
result = helper.leave_note_for_next_session(sender_id, note_type, title, content, priority, recipient_id)
|
|
410
|
+
print(json.dumps({"id": result}, indent=2))
|
|
411
|
+
|
|
412
|
+
elif command == "notes":
|
|
413
|
+
recipient_id = int(sys.argv[2]) if len(sys.argv) > 2 else None
|
|
414
|
+
result = helper.get_notes_for_next_session(recipient_id=recipient_id)
|
|
415
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
416
|
+
|
|
417
|
+
elif command == "respond":
|
|
418
|
+
if len(sys.argv) < 5:
|
|
419
|
+
print("Usage: respond <sender_id> <original_note_id> <response_type> <content>")
|
|
420
|
+
sys.exit(1)
|
|
421
|
+
sender_id = int(sys.argv[2])
|
|
422
|
+
original_note_id = int(sys.argv[3])
|
|
423
|
+
response_type = sys.argv[4]
|
|
424
|
+
content = sys.argv[5] if len(sys.argv) > 5 else ""
|
|
425
|
+
|
|
426
|
+
result = helper.respond_to_previous_session(sender_id, original_note_id, response_type, content)
|
|
427
|
+
print(json.dumps({"id": result}, indent=2))
|
|
428
|
+
|
|
429
|
+
elif command == "insights":
|
|
430
|
+
ai_id = int(sys.argv[2]) if len(sys.argv) > 2 else None
|
|
431
|
+
insight_type = sys.argv[3] if len(sys.argv) > 3 else None
|
|
432
|
+
result = helper.get_insights(ai_id=ai_id, insight_type=insight_type)
|
|
433
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
434
|
+
|
|
435
|
+
elif command == "stats":
|
|
436
|
+
# Get stats from all relevant tables
|
|
437
|
+
stats = {
|
|
438
|
+
"ai_profiles": len(helper.query("SELECT id FROM ai_profiles")),
|
|
439
|
+
"conversations": len(helper.query("SELECT id FROM ai_conversations")),
|
|
440
|
+
"messages": len(helper.query("SELECT id FROM ai_messages")),
|
|
441
|
+
"notes": len(helper.query("SELECT id FROM ai_next_session_notes")),
|
|
442
|
+
"responses": len(helper.query("SELECT id FROM ai_previous_session_responses")),
|
|
443
|
+
"insights": len(helper.query("SELECT id FROM ai_insights")),
|
|
444
|
+
"notifications": len(helper.query("SELECT id FROM ai_notifications")),
|
|
445
|
+
"unread_notifications": len(helper.query("SELECT id FROM ai_notifications WHERE is_read = 0")),
|
|
446
|
+
"collaborations": len(helper.query("SELECT id FROM ai_collaborations"))
|
|
447
|
+
}
|
|
448
|
+
print(json.dumps(stats, indent=2))
|
|
449
|
+
|
|
450
|
+
elif command == "notify":
|
|
451
|
+
if len(sys.argv) < 5:
|
|
452
|
+
print("Usage: notify <sender_id> <title> <content> [type] [priority] [recipient_id]")
|
|
453
|
+
sys.exit(1)
|
|
454
|
+
sender_id = int(sys.argv[2])
|
|
455
|
+
title = sys.argv[3]
|
|
456
|
+
content = sys.argv[4]
|
|
457
|
+
notification_type = sys.argv[5] if len(sys.argv) > 5 else "general"
|
|
458
|
+
priority = sys.argv[6] if len(sys.argv) > 6 else "normal"
|
|
459
|
+
recipient_id = int(sys.argv[7]) if len(sys.argv) > 7 else None
|
|
460
|
+
|
|
461
|
+
result = helper.send_notification(sender_id, title, content, notification_type, priority, recipient_id)
|
|
462
|
+
print(json.dumps({"id": result}, indent=2))
|
|
463
|
+
|
|
464
|
+
elif command == "notifications":
|
|
465
|
+
recipient_id = int(sys.argv[2]) if len(sys.argv) > 2 else None
|
|
466
|
+
unread_only = sys.argv[3].lower() == 'true' if len(sys.argv) > 3 else False
|
|
467
|
+
result = helper.get_notifications(recipient_id, unread_only)
|
|
468
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
469
|
+
|
|
470
|
+
elif command == "notification_stats":
|
|
471
|
+
result = helper.get_notification_stats()
|
|
472
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
473
|
+
|
|
474
|
+
elif command == "unread_notifications":
|
|
475
|
+
recipient_id = int(sys.argv[2]) if len(sys.argv) > 2 else None
|
|
476
|
+
result = helper.get_unread_notifications_view()
|
|
477
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
478
|
+
|
|
479
|
+
elif command == "mark_read":
|
|
480
|
+
if len(sys.argv) < 3:
|
|
481
|
+
print("Usage: mark_read <notification_id>")
|
|
482
|
+
sys.exit(1)
|
|
483
|
+
notification_id = int(sys.argv[2])
|
|
484
|
+
result = helper.mark_notification_as_read(notification_id)
|
|
485
|
+
print(json.dumps({"success": result}, indent=2))
|
|
486
|
+
|
|
487
|
+
elif command == "subscribe":
|
|
488
|
+
if len(sys.argv) < 4:
|
|
489
|
+
print("Usage: subscribe <ai_profile_id> <notification_type>")
|
|
490
|
+
sys.exit(1)
|
|
491
|
+
ai_profile_id = int(sys.argv[2])
|
|
492
|
+
notification_type = sys.argv[3]
|
|
493
|
+
result = helper.subscribe_to_notification_type(ai_profile_id, notification_type)
|
|
494
|
+
print(json.dumps({"success": result}, indent=2))
|
|
495
|
+
|
|
496
|
+
else:
|
|
497
|
+
print(f"Unknown command: {command}")
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
if __name__ == "__main__":
|
|
501
|
+
import os
|
|
502
|
+
main()
|