cli-memory-os 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.
- cli/__init__.py +1 -0
- cli/commands/__init__.py +1 -0
- cli/commands/benchmark.py +151 -0
- cli/commands/config_cmd.py +57 -0
- cli/commands/doctor.py +61 -0
- cli/commands/export.py +106 -0
- cli/commands/import_cmd.py +143 -0
- cli/commands/init.py +205 -0
- cli/commands/logs.py +38 -0
- cli/commands/monitor.py +63 -0
- cli/commands/plugins.py +37 -0
- cli/commands/start.py +24 -0
- cli/commands/status.py +92 -0
- cli/commands/stop.py +19 -0
- cli/commands/version.py +25 -0
- cli/commands/workspace.py +154 -0
- cli/main.py +496 -0
- cli/parser.py +188 -0
- cli_memory_os-0.1.0.dist-info/METADATA +231 -0
- cli_memory_os-0.1.0.dist-info/RECORD +50 -0
- cli_memory_os-0.1.0.dist-info/WHEEL +5 -0
- cli_memory_os-0.1.0.dist-info/entry_points.txt +2 -0
- cli_memory_os-0.1.0.dist-info/licenses/LICENSE +21 -0
- cli_memory_os-0.1.0.dist-info/top_level.txt +6 -0
- connectors/__init__.py +1 -0
- connectors/base.py +39 -0
- connectors/github.py +273 -0
- connectors/gmail.py +116 -0
- connectors/notion.py +156 -0
- connectors/registry.py +60 -0
- core/__init__.py +1 -0
- core/chunker.py +136 -0
- core/context_builder.py +407 -0
- core/embedder.py +42 -0
- core/llm.py +301 -0
- core/vector_store.py +629 -0
- infrastructure/__init__.py +1 -0
- infrastructure/compose.py +136 -0
- infrastructure/config.py +280 -0
- infrastructure/docker.py +61 -0
- infrastructure/health.py +338 -0
- infrastructure/observability.py +160 -0
- infrastructure/workspace.py +152 -0
- models/__init__.py +1 -0
- models/memory.py +28 -0
- storage/__init__.py +1 -0
- storage/db.py +528 -0
- storage/graph.py +324 -0
- storage/schema.sql +57 -0
- storage/tech_detector.py +117 -0
storage/db.py
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
DB_PATH = "memory.db"
|
|
5
|
+
SCHEMA_PATH = os.path.join(os.path.dirname(__file__), "schema.sql")
|
|
6
|
+
|
|
7
|
+
_initialized_dbs = set()
|
|
8
|
+
|
|
9
|
+
def get_connection():
|
|
10
|
+
global _initialized_dbs
|
|
11
|
+
db_path = os.getenv("MEMORY_OS_DB_PATH", DB_PATH)
|
|
12
|
+
abs_path = os.path.abspath(db_path)
|
|
13
|
+
|
|
14
|
+
# Ensure directory exists
|
|
15
|
+
parent = os.path.dirname(abs_path)
|
|
16
|
+
if parent:
|
|
17
|
+
os.makedirs(parent, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
conn = sqlite3.connect(abs_path)
|
|
20
|
+
conn.row_factory = sqlite3.Row
|
|
21
|
+
|
|
22
|
+
if abs_path not in _initialized_dbs:
|
|
23
|
+
# Check if the schema is initialized (e.g. check if document_chunks table exists)
|
|
24
|
+
cursor = conn.cursor()
|
|
25
|
+
try:
|
|
26
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='document_chunks'")
|
|
27
|
+
table_exists = cursor.fetchone()
|
|
28
|
+
except sqlite3.OperationalError:
|
|
29
|
+
table_exists = False
|
|
30
|
+
|
|
31
|
+
if not table_exists:
|
|
32
|
+
_initialized_dbs.add(abs_path)
|
|
33
|
+
conn.close()
|
|
34
|
+
init_db()
|
|
35
|
+
conn = sqlite3.connect(abs_path)
|
|
36
|
+
conn.row_factory = sqlite3.Row
|
|
37
|
+
else:
|
|
38
|
+
_initialized_dbs.add(abs_path)
|
|
39
|
+
|
|
40
|
+
return conn
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def init_db():
|
|
45
|
+
conn = get_connection()
|
|
46
|
+
cursor = conn.cursor()
|
|
47
|
+
# Check if repositories table exists and needs recreation (e.g. legacy schema)
|
|
48
|
+
cursor.execute("PRAGMA table_info(repositories)")
|
|
49
|
+
columns = [row[1] for row in cursor.fetchall()]
|
|
50
|
+
if columns and "visibility" not in columns:
|
|
51
|
+
cursor.execute("DROP TABLE repositories")
|
|
52
|
+
cursor.execute("DROP TABLE IF EXISTS repository_documents")
|
|
53
|
+
|
|
54
|
+
# Check if emails table exists and needs recreation (e.g. legacy schema without message_id)
|
|
55
|
+
cursor.execute("PRAGMA table_info(emails)")
|
|
56
|
+
email_columns = [row[1] for row in cursor.fetchall()]
|
|
57
|
+
if email_columns and "message_id" not in email_columns:
|
|
58
|
+
cursor.execute("DROP TABLE emails")
|
|
59
|
+
|
|
60
|
+
if os.path.exists(SCHEMA_PATH):
|
|
61
|
+
with open(SCHEMA_PATH, "r", encoding="utf-8") as f:
|
|
62
|
+
schema_sql = f.read()
|
|
63
|
+
conn.executescript(schema_sql)
|
|
64
|
+
conn.commit()
|
|
65
|
+
conn.close()
|
|
66
|
+
|
|
67
|
+
def insert_repository(repo):
|
|
68
|
+
conn = get_connection()
|
|
69
|
+
cursor = conn.cursor()
|
|
70
|
+
cursor.execute("SELECT id FROM repositories WHERE repo_name = ?", (repo.repo_name,))
|
|
71
|
+
row = cursor.fetchone()
|
|
72
|
+
if row:
|
|
73
|
+
cursor.execute(
|
|
74
|
+
"""
|
|
75
|
+
UPDATE repositories
|
|
76
|
+
SET description = ?, language = ?, visibility = ?, stars = ?, forks = ?, open_issues = ?, default_branch = ?, updated_at = ?, url = ?
|
|
77
|
+
WHERE id = ?
|
|
78
|
+
""",
|
|
79
|
+
(repo.description, repo.language, repo.visibility, repo.stars, repo.forks, repo.open_issues, repo.default_branch, repo.updated_at, repo.url, row[0])
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
cursor.execute(
|
|
83
|
+
"""
|
|
84
|
+
INSERT INTO repositories (repo_name, description, language, visibility, stars, forks, open_issues, default_branch, updated_at, url)
|
|
85
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
86
|
+
""",
|
|
87
|
+
(repo.repo_name, repo.description, repo.language, repo.visibility, repo.stars, repo.forks, repo.open_issues, repo.default_branch, repo.updated_at, repo.url)
|
|
88
|
+
)
|
|
89
|
+
conn.commit()
|
|
90
|
+
conn.close()
|
|
91
|
+
|
|
92
|
+
def insert_repository_document(doc):
|
|
93
|
+
conn = get_connection()
|
|
94
|
+
cursor = conn.cursor()
|
|
95
|
+
cursor.execute(
|
|
96
|
+
"SELECT id FROM repository_documents WHERE repo_name = ? AND file_name = ?",
|
|
97
|
+
(doc.repo_name, doc.file_name)
|
|
98
|
+
)
|
|
99
|
+
row = cursor.fetchone()
|
|
100
|
+
if row:
|
|
101
|
+
cursor.execute(
|
|
102
|
+
"UPDATE repository_documents SET content = ?, source = ?, synced_at = ? WHERE id = ?",
|
|
103
|
+
(doc.content, doc.source, doc.synced_at, row[0])
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
cursor.execute(
|
|
107
|
+
"INSERT INTO repository_documents (repo_name, file_name, content, source, synced_at) VALUES (?, ?, ?, ?, ?)",
|
|
108
|
+
(doc.repo_name, doc.file_name, doc.content, doc.source, doc.synced_at)
|
|
109
|
+
)
|
|
110
|
+
conn.commit()
|
|
111
|
+
conn.close()
|
|
112
|
+
|
|
113
|
+
def insert_email(email):
|
|
114
|
+
conn = get_connection()
|
|
115
|
+
cursor = conn.cursor()
|
|
116
|
+
if email.message_id:
|
|
117
|
+
cursor.execute("SELECT id FROM emails WHERE message_id = ?", (email.message_id,))
|
|
118
|
+
else:
|
|
119
|
+
cursor.execute(
|
|
120
|
+
"SELECT id FROM emails WHERE subject = ? AND sender = ? AND received_at = ?",
|
|
121
|
+
(email.subject, email.sender, email.received_at)
|
|
122
|
+
)
|
|
123
|
+
row = cursor.fetchone()
|
|
124
|
+
if row:
|
|
125
|
+
cursor.execute(
|
|
126
|
+
"UPDATE emails SET snippet = ?, subject = ?, sender = ?, received_at = ? WHERE id = ?",
|
|
127
|
+
(email.snippet, email.subject, email.sender, email.received_at, row[0])
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
cursor.execute(
|
|
131
|
+
"INSERT INTO emails (message_id, subject, sender, snippet, received_at) VALUES (?, ?, ?, ?, ?)",
|
|
132
|
+
(email.message_id, email.subject, email.sender, email.snippet, email.received_at)
|
|
133
|
+
)
|
|
134
|
+
conn.commit()
|
|
135
|
+
conn.close()
|
|
136
|
+
|
|
137
|
+
def get_repo_count() -> int:
|
|
138
|
+
conn = get_connection()
|
|
139
|
+
cursor = conn.cursor()
|
|
140
|
+
cursor.execute("SELECT COUNT(*) FROM repositories")
|
|
141
|
+
count = cursor.fetchone()[0]
|
|
142
|
+
conn.close()
|
|
143
|
+
return count
|
|
144
|
+
|
|
145
|
+
def get_repository_document_count() -> int:
|
|
146
|
+
conn = get_connection()
|
|
147
|
+
cursor = conn.cursor()
|
|
148
|
+
cursor.execute("SELECT COUNT(*) FROM repository_documents")
|
|
149
|
+
count = cursor.fetchone()[0]
|
|
150
|
+
conn.close()
|
|
151
|
+
return count
|
|
152
|
+
|
|
153
|
+
def get_email_count() -> int:
|
|
154
|
+
conn = get_connection()
|
|
155
|
+
cursor = conn.cursor()
|
|
156
|
+
cursor.execute("SELECT COUNT(*) FROM emails")
|
|
157
|
+
count = cursor.fetchone()[0]
|
|
158
|
+
conn.close()
|
|
159
|
+
return count
|
|
160
|
+
|
|
161
|
+
def get_repository_details(repo_name: str):
|
|
162
|
+
conn = get_connection()
|
|
163
|
+
cursor = conn.cursor()
|
|
164
|
+
cursor.execute(
|
|
165
|
+
"SELECT repo_name, description, language, visibility, stars, forks, open_issues, default_branch, updated_at, url FROM repositories WHERE LOWER(repo_name) = LOWER(?)",
|
|
166
|
+
(repo_name,)
|
|
167
|
+
)
|
|
168
|
+
repo_row = cursor.fetchone()
|
|
169
|
+
if not repo_row:
|
|
170
|
+
conn.close()
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
matched_name = repo_row[0]
|
|
174
|
+
|
|
175
|
+
# Get files stored for this repository
|
|
176
|
+
cursor.execute(
|
|
177
|
+
"SELECT file_name FROM repository_documents WHERE LOWER(repo_name) = LOWER(?)",
|
|
178
|
+
(repo_name,)
|
|
179
|
+
)
|
|
180
|
+
files = [row[0] for row in cursor.fetchall()]
|
|
181
|
+
|
|
182
|
+
# Get README content
|
|
183
|
+
cursor.execute(
|
|
184
|
+
"SELECT content FROM repository_documents WHERE LOWER(repo_name) = LOWER(?) AND LOWER(file_name) = 'readme.md'",
|
|
185
|
+
(repo_name,)
|
|
186
|
+
)
|
|
187
|
+
readme_row = cursor.fetchone()
|
|
188
|
+
readme_content = readme_row[0] if readme_row else None
|
|
189
|
+
|
|
190
|
+
conn.close()
|
|
191
|
+
return {
|
|
192
|
+
"repo_name": matched_name,
|
|
193
|
+
"description": repo_row[1],
|
|
194
|
+
"language": repo_row[2],
|
|
195
|
+
"visibility": repo_row[3],
|
|
196
|
+
"stars": repo_row[4],
|
|
197
|
+
"forks": repo_row[5],
|
|
198
|
+
"open_issues": repo_row[6],
|
|
199
|
+
"default_branch": repo_row[7],
|
|
200
|
+
"updated_at": repo_row[8],
|
|
201
|
+
"url": repo_row[9],
|
|
202
|
+
"files": files,
|
|
203
|
+
"readme": readme_content
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
def clear_all():
|
|
207
|
+
conn = get_connection()
|
|
208
|
+
cursor = conn.cursor()
|
|
209
|
+
cursor.execute("DELETE FROM repositories")
|
|
210
|
+
cursor.execute("DELETE FROM repository_documents")
|
|
211
|
+
cursor.execute("DELETE FROM emails")
|
|
212
|
+
cursor.execute("DELETE FROM document_chunks")
|
|
213
|
+
cursor.execute("DELETE FROM graph_relationships")
|
|
214
|
+
cursor.execute("DELETE FROM graph_nodes")
|
|
215
|
+
conn.commit()
|
|
216
|
+
conn.close()
|
|
217
|
+
|
|
218
|
+
def search_local_knowledge_ranked(query: str, repo_filter: str = None) -> list:
|
|
219
|
+
"""Ranked search across repositories, documents, and emails.
|
|
220
|
+
|
|
221
|
+
Applies configurable boost for repository scores and weight for email scores.
|
|
222
|
+
Adds ``source_type`` to each result and logs detailed diagnostics.
|
|
223
|
+
"""
|
|
224
|
+
conn = get_connection()
|
|
225
|
+
cursor = conn.cursor()
|
|
226
|
+
|
|
227
|
+
if repo_filter:
|
|
228
|
+
if isinstance(repo_filter, list):
|
|
229
|
+
placeholders = ",".join(["?"] * len(repo_filter))
|
|
230
|
+
cursor.execute(
|
|
231
|
+
f"SELECT repo_name, language, description FROM repositories WHERE LOWER(repo_name) IN ({placeholders})",
|
|
232
|
+
[r.lower() for r in repo_filter]
|
|
233
|
+
)
|
|
234
|
+
repos = cursor.fetchall()
|
|
235
|
+
|
|
236
|
+
cursor.execute(
|
|
237
|
+
f"SELECT repo_name, file_name, content FROM repository_documents WHERE LOWER(repo_name) IN ({placeholders})",
|
|
238
|
+
[r.lower() for r in repo_filter]
|
|
239
|
+
)
|
|
240
|
+
docs = cursor.fetchall()
|
|
241
|
+
|
|
242
|
+
emails = []
|
|
243
|
+
else:
|
|
244
|
+
cursor.execute("SELECT repo_name, language, description FROM repositories WHERE LOWER(repo_name) = LOWER(?)", (repo_filter,))
|
|
245
|
+
repos = cursor.fetchall()
|
|
246
|
+
|
|
247
|
+
cursor.execute("SELECT repo_name, file_name, content FROM repository_documents WHERE LOWER(repo_name) = LOWER(?)", (repo_filter,))
|
|
248
|
+
docs = cursor.fetchall()
|
|
249
|
+
|
|
250
|
+
emails = []
|
|
251
|
+
else:
|
|
252
|
+
# Fetch all data for scoring
|
|
253
|
+
cursor.execute("SELECT repo_name, language, description FROM repositories")
|
|
254
|
+
repos = cursor.fetchall()
|
|
255
|
+
|
|
256
|
+
cursor.execute("SELECT repo_name, file_name, content FROM repository_documents")
|
|
257
|
+
docs = cursor.fetchall()
|
|
258
|
+
|
|
259
|
+
cursor.execute("SELECT subject, sender, snippet FROM emails")
|
|
260
|
+
emails = cursor.fetchall()
|
|
261
|
+
conn.close()
|
|
262
|
+
|
|
263
|
+
# Configurable boosts (default = 1.0 i.e., no change)
|
|
264
|
+
repo_boost = float(os.getenv('REPO_SCORE_BOOST', '1.0'))
|
|
265
|
+
email_weight = float(os.getenv('EMAIL_SCORE_WEIGHT', '1.0'))
|
|
266
|
+
|
|
267
|
+
ranked_results = []
|
|
268
|
+
query_lower = query.lower()
|
|
269
|
+
logger = logging.getLogger(__name__)
|
|
270
|
+
|
|
271
|
+
# --- Score repositories ---
|
|
272
|
+
for repo_name, language, description in repos:
|
|
273
|
+
score = 0
|
|
274
|
+
if repo_name and query_lower in repo_name.lower():
|
|
275
|
+
score += 10
|
|
276
|
+
if description and query_lower in description.lower():
|
|
277
|
+
score += 8
|
|
278
|
+
if language and query_lower in language.lower():
|
|
279
|
+
score += 5
|
|
280
|
+
if score > 0:
|
|
281
|
+
score = int(score * repo_boost)
|
|
282
|
+
result = {
|
|
283
|
+
"type": "repository",
|
|
284
|
+
"score": score,
|
|
285
|
+
"repo_name": repo_name,
|
|
286
|
+
"language": language,
|
|
287
|
+
"description": description,
|
|
288
|
+
"source_type": "repository",
|
|
289
|
+
}
|
|
290
|
+
ranked_results.append(result)
|
|
291
|
+
logger.debug(f"Retrieval result - {result}")
|
|
292
|
+
|
|
293
|
+
# --- Score documents ---
|
|
294
|
+
for repo_name, file_name, content in docs:
|
|
295
|
+
score = 0
|
|
296
|
+
content_lower = content.lower() if content else ""
|
|
297
|
+
file_name_lower = file_name.lower() if file_name else ""
|
|
298
|
+
if query_lower in file_name_lower or query_lower in content_lower:
|
|
299
|
+
if file_name_lower == "readme.md":
|
|
300
|
+
score += 6
|
|
301
|
+
elif file_name_lower == "package.json":
|
|
302
|
+
score += 4
|
|
303
|
+
else:
|
|
304
|
+
score += 3
|
|
305
|
+
if score > 0:
|
|
306
|
+
result = {
|
|
307
|
+
"type": "document",
|
|
308
|
+
"score": score,
|
|
309
|
+
"repo_name": repo_name,
|
|
310
|
+
"file_name": file_name,
|
|
311
|
+
"content": content,
|
|
312
|
+
"source_type": "document",
|
|
313
|
+
}
|
|
314
|
+
ranked_results.append(result)
|
|
315
|
+
logger.debug(f"Retrieval result - {result}")
|
|
316
|
+
|
|
317
|
+
# --- Score emails ---
|
|
318
|
+
for subject, sender, snippet in emails:
|
|
319
|
+
score = 0
|
|
320
|
+
subj_l = subject.lower() if subject else ""
|
|
321
|
+
send_l = sender.lower() if sender else ""
|
|
322
|
+
snip_l = snippet.lower() if snippet else ""
|
|
323
|
+
if query_lower in subj_l or query_lower in send_l or query_lower in snip_l:
|
|
324
|
+
score += 2
|
|
325
|
+
if score > 0:
|
|
326
|
+
score = int(score * email_weight)
|
|
327
|
+
result = {
|
|
328
|
+
"type": "email",
|
|
329
|
+
"score": score,
|
|
330
|
+
"subject": subject,
|
|
331
|
+
"sender": sender,
|
|
332
|
+
"snippet": snippet,
|
|
333
|
+
"source_type": "email",
|
|
334
|
+
}
|
|
335
|
+
ranked_results.append(result)
|
|
336
|
+
logger.debug(f"Retrieval result - {result}")
|
|
337
|
+
|
|
338
|
+
# Sort descending by score, then stable alphabetical tie‑breaker
|
|
339
|
+
ranked_results.sort(key=lambda x: (-x["score"], x.get("repo_name") or x.get("subject") or ""))
|
|
340
|
+
return ranked_results
|
|
341
|
+
|
|
342
|
+
def get_repository_files(repo_name: str) -> list:
|
|
343
|
+
conn = get_connection()
|
|
344
|
+
cursor = conn.cursor()
|
|
345
|
+
cursor.execute("SELECT file_name FROM repository_documents WHERE LOWER(repo_name) = LOWER(?)", (repo_name,))
|
|
346
|
+
files = [row[0] for row in cursor.fetchall()]
|
|
347
|
+
conn.close()
|
|
348
|
+
return files
|
|
349
|
+
|
|
350
|
+
def get_repository_readme(repo_name: str) -> str:
|
|
351
|
+
conn = get_connection()
|
|
352
|
+
cursor = conn.cursor()
|
|
353
|
+
cursor.execute("SELECT content FROM repository_documents WHERE LOWER(repo_name) = LOWER(?) AND LOWER(file_name) = 'readme.md'", (repo_name,))
|
|
354
|
+
row = cursor.fetchone()
|
|
355
|
+
readme = row[0] if row else None
|
|
356
|
+
conn.close()
|
|
357
|
+
return readme
|
|
358
|
+
|
|
359
|
+
def get_all_repositories() -> list:
|
|
360
|
+
conn = get_connection()
|
|
361
|
+
cursor = conn.cursor()
|
|
362
|
+
cursor.execute("SELECT repo_name, language, description, stars, forks, updated_at FROM repositories")
|
|
363
|
+
repos = [
|
|
364
|
+
{
|
|
365
|
+
"repo_name": row[0],
|
|
366
|
+
"language": row[1],
|
|
367
|
+
"description": row[2],
|
|
368
|
+
"stars": row[3],
|
|
369
|
+
"forks": row[4],
|
|
370
|
+
"updated_at": row[5]
|
|
371
|
+
}
|
|
372
|
+
for row in cursor.fetchall()
|
|
373
|
+
]
|
|
374
|
+
conn.close()
|
|
375
|
+
return repos
|
|
376
|
+
|
|
377
|
+
def get_all_documents() -> list:
|
|
378
|
+
conn = get_connection()
|
|
379
|
+
cursor = conn.cursor()
|
|
380
|
+
cursor.execute("SELECT repo_name, file_name, content FROM repository_documents")
|
|
381
|
+
docs = [
|
|
382
|
+
{"repo_name": row[0], "file_name": row[1], "content": row[2]}
|
|
383
|
+
for row in cursor.fetchall()
|
|
384
|
+
]
|
|
385
|
+
conn.close()
|
|
386
|
+
return docs
|
|
387
|
+
|
|
388
|
+
def insert_document_chunk(repository_name: str, document_name: str, source_type: str, chunk_text: str, chunk_index: int, created_at: str):
|
|
389
|
+
conn = get_connection()
|
|
390
|
+
cursor = conn.cursor()
|
|
391
|
+
cursor.execute(
|
|
392
|
+
"""
|
|
393
|
+
INSERT INTO document_chunks (repository_name, document_name, source_type, chunk_text, chunk_index, created_at)
|
|
394
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
395
|
+
""",
|
|
396
|
+
(repository_name, document_name, source_type, chunk_text, chunk_index, created_at)
|
|
397
|
+
)
|
|
398
|
+
conn.commit()
|
|
399
|
+
conn.close()
|
|
400
|
+
|
|
401
|
+
def clear_document_chunks():
|
|
402
|
+
conn = get_connection()
|
|
403
|
+
cursor = conn.cursor()
|
|
404
|
+
cursor.execute("DELETE FROM document_chunks")
|
|
405
|
+
cursor.execute("DELETE FROM sqlite_sequence WHERE name = 'document_chunks'")
|
|
406
|
+
conn.commit()
|
|
407
|
+
conn.close()
|
|
408
|
+
|
|
409
|
+
def get_document_chunk_count() -> int:
|
|
410
|
+
conn = get_connection()
|
|
411
|
+
cursor = conn.cursor()
|
|
412
|
+
cursor.execute("SELECT COUNT(*) FROM document_chunks")
|
|
413
|
+
count = cursor.fetchone()[0]
|
|
414
|
+
conn.close()
|
|
415
|
+
return count
|
|
416
|
+
|
|
417
|
+
def get_all_document_chunks() -> list:
|
|
418
|
+
conn = get_connection()
|
|
419
|
+
cursor = conn.cursor()
|
|
420
|
+
cursor.execute("SELECT id, repository_name, document_name, source_type, chunk_text, chunk_index FROM document_chunks")
|
|
421
|
+
chunks = [
|
|
422
|
+
{
|
|
423
|
+
"id": row[0],
|
|
424
|
+
"repository_name": row[1],
|
|
425
|
+
"document_name": row[2],
|
|
426
|
+
"source_type": row[3],
|
|
427
|
+
"chunk_text": row[4],
|
|
428
|
+
"chunk_index": row[5]
|
|
429
|
+
}
|
|
430
|
+
for row in cursor.fetchall()
|
|
431
|
+
]
|
|
432
|
+
conn.close()
|
|
433
|
+
return chunks
|
|
434
|
+
|
|
435
|
+
def get_all_emails() -> list:
|
|
436
|
+
conn = get_connection()
|
|
437
|
+
cursor = conn.cursor()
|
|
438
|
+
cursor.execute("SELECT message_id, subject, sender, snippet, received_at FROM emails")
|
|
439
|
+
emails = [
|
|
440
|
+
{
|
|
441
|
+
"message_id": row[0],
|
|
442
|
+
"subject": row[1],
|
|
443
|
+
"sender": row[2],
|
|
444
|
+
"snippet": row[3],
|
|
445
|
+
"received_at": row[4]
|
|
446
|
+
}
|
|
447
|
+
for row in cursor.fetchall()
|
|
448
|
+
]
|
|
449
|
+
conn.close()
|
|
450
|
+
return emails
|
|
451
|
+
|
|
452
|
+
def insert_fallback_node(node_id: str, label: str, name: str):
|
|
453
|
+
conn = get_connection()
|
|
454
|
+
cursor = conn.cursor()
|
|
455
|
+
cursor.execute(
|
|
456
|
+
"INSERT OR REPLACE INTO graph_nodes (id, label, name) VALUES (?, ?, ?)",
|
|
457
|
+
(node_id, label, name)
|
|
458
|
+
)
|
|
459
|
+
conn.commit()
|
|
460
|
+
conn.close()
|
|
461
|
+
|
|
462
|
+
def insert_fallback_relationship(rel_id: str, source_id: str, target_id: str, type_str: str):
|
|
463
|
+
conn = get_connection()
|
|
464
|
+
cursor = conn.cursor()
|
|
465
|
+
cursor.execute(
|
|
466
|
+
"INSERT OR REPLACE INTO graph_relationships (id, source_id, target_id, type) VALUES (?, ?, ?, ?)",
|
|
467
|
+
(rel_id, source_id, target_id, type_str)
|
|
468
|
+
)
|
|
469
|
+
conn.commit()
|
|
470
|
+
conn.close()
|
|
471
|
+
|
|
472
|
+
def get_fallback_relationships(node_id: str) -> list:
|
|
473
|
+
conn = get_connection()
|
|
474
|
+
cursor = conn.cursor()
|
|
475
|
+
# Find relationships where node_id is either source or target
|
|
476
|
+
cursor.execute(
|
|
477
|
+
"""
|
|
478
|
+
SELECT r.type, s.label, s.name, t.label, t.name
|
|
479
|
+
FROM graph_relationships r
|
|
480
|
+
JOIN graph_nodes s ON r.source_id = s.id
|
|
481
|
+
JOIN graph_nodes t ON r.target_id = t.id
|
|
482
|
+
WHERE r.source_id = ? OR r.target_id = ?
|
|
483
|
+
""",
|
|
484
|
+
(node_id, node_id)
|
|
485
|
+
)
|
|
486
|
+
results = [
|
|
487
|
+
{
|
|
488
|
+
"type": row[0],
|
|
489
|
+
"source_label": row[1],
|
|
490
|
+
"source_name": row[2],
|
|
491
|
+
"target_label": row[3],
|
|
492
|
+
"target_name": row[4]
|
|
493
|
+
}
|
|
494
|
+
for row in cursor.fetchall()
|
|
495
|
+
]
|
|
496
|
+
conn.close()
|
|
497
|
+
return results
|
|
498
|
+
|
|
499
|
+
def clear_fallback_graph():
|
|
500
|
+
conn = get_connection()
|
|
501
|
+
cursor = conn.cursor()
|
|
502
|
+
cursor.execute("DELETE FROM graph_relationships")
|
|
503
|
+
cursor.execute("DELETE FROM graph_nodes")
|
|
504
|
+
conn.commit()
|
|
505
|
+
conn.close()
|
|
506
|
+
|
|
507
|
+
def delete_data_before_date(date_str: str) -> dict:
|
|
508
|
+
conn = get_connection()
|
|
509
|
+
cursor = conn.cursor()
|
|
510
|
+
|
|
511
|
+
# We compare string-wise since ISO dates sort correctly
|
|
512
|
+
cursor.execute("DELETE FROM emails WHERE received_at < ?", (date_str,))
|
|
513
|
+
emails_deleted = cursor.rowcount
|
|
514
|
+
|
|
515
|
+
cursor.execute("DELETE FROM repository_documents WHERE synced_at < ?", (date_str,))
|
|
516
|
+
docs_deleted = cursor.rowcount
|
|
517
|
+
|
|
518
|
+
cursor.execute("DELETE FROM document_chunks WHERE created_at < ?", (date_str,))
|
|
519
|
+
chunks_deleted = cursor.rowcount
|
|
520
|
+
|
|
521
|
+
conn.commit()
|
|
522
|
+
conn.close()
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
"emails": emails_deleted,
|
|
526
|
+
"documents": docs_deleted,
|
|
527
|
+
"chunks": chunks_deleted
|
|
528
|
+
}
|