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
connectors/gmail.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from composio import Composio
|
|
2
|
+
from models.memory import Email
|
|
3
|
+
from storage.db import insert_email, get_connection
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
def sync_gmail():
|
|
9
|
+
try:
|
|
10
|
+
try:
|
|
11
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
12
|
+
except Exception:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
c = Composio()
|
|
16
|
+
user_id = os.getenv("COMPOSIO_USER_ID", "user_123")
|
|
17
|
+
s = c.create(user_id=user_id)
|
|
18
|
+
|
|
19
|
+
# Verify connection
|
|
20
|
+
toolkits_info = s.toolkits()
|
|
21
|
+
gmail_tk = next((t for t in toolkits_info.items if t.slug == "gmail"), None)
|
|
22
|
+
if not gmail_tk or not (gmail_tk.connection and gmail_tk.connection.is_active):
|
|
23
|
+
print("Gmail connection not active.")
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
# Fetch emails
|
|
27
|
+
resp = s.execute(tool_slug="gmail_fetch_emails", arguments={"max_results": 50, "include_payload": True})
|
|
28
|
+
if not resp or resp.error or not resp.data:
|
|
29
|
+
print("No emails found.")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
emails = resp.data.get("messages", [])
|
|
33
|
+
if not emails:
|
|
34
|
+
# Check response_data structure just in case
|
|
35
|
+
emails = resp.data.get("response_data", {}).get("messages", [])
|
|
36
|
+
if not emails:
|
|
37
|
+
print("No emails found.")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
print(f"Found {len(emails)} emails")
|
|
41
|
+
|
|
42
|
+
conn = get_connection()
|
|
43
|
+
cursor = conn.cursor()
|
|
44
|
+
|
|
45
|
+
synced_count = 0
|
|
46
|
+
skipped_count = 0
|
|
47
|
+
|
|
48
|
+
for email in emails:
|
|
49
|
+
message_id = email.get("id") or email.get("messageId") or email.get("threadId") or ""
|
|
50
|
+
|
|
51
|
+
# Check if email is already in SQLite
|
|
52
|
+
if message_id:
|
|
53
|
+
cursor.execute("SELECT id FROM emails WHERE message_id = ?", (message_id,))
|
|
54
|
+
if cursor.fetchone():
|
|
55
|
+
skipped_count += 1
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
subject = email.get("subject") or "No Subject"
|
|
59
|
+
sender = email.get("sender") or email.get("from") or "Unknown Sender"
|
|
60
|
+
snippet = email.get("messageText") or email.get("snippet") or ""
|
|
61
|
+
received_at = email.get("messageTimestamp") or ""
|
|
62
|
+
date_str = received_at[:10] if len(received_at) >= 10 else received_at
|
|
63
|
+
|
|
64
|
+
# Save to SQLite
|
|
65
|
+
db_email = Email(
|
|
66
|
+
message_id=message_id,
|
|
67
|
+
subject=subject,
|
|
68
|
+
sender=sender,
|
|
69
|
+
snippet=snippet,
|
|
70
|
+
received_at=received_at
|
|
71
|
+
)
|
|
72
|
+
insert_email(db_email)
|
|
73
|
+
synced_count += 1
|
|
74
|
+
|
|
75
|
+
# Print to terminal
|
|
76
|
+
print("--------------------------------------------------")
|
|
77
|
+
print(f"Subject: {subject}")
|
|
78
|
+
print(f"From: {sender}")
|
|
79
|
+
print(f"Date: {date_str}")
|
|
80
|
+
print("--------------------------------------------------")
|
|
81
|
+
|
|
82
|
+
conn.close()
|
|
83
|
+
print(f"Gmail Sync Complete: {synced_count} emails synced, {skipped_count} emails skipped.")
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f"Error during Gmail sync: {e}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
from connectors.base import BaseConnector
|
|
90
|
+
from connectors.registry import register
|
|
91
|
+
|
|
92
|
+
@register
|
|
93
|
+
class GmailConnector(BaseConnector):
|
|
94
|
+
name = "Gmail"
|
|
95
|
+
slug = "gmail"
|
|
96
|
+
|
|
97
|
+
def authenticate(self) -> bool:
|
|
98
|
+
try:
|
|
99
|
+
c = Composio()
|
|
100
|
+
user_id = os.getenv("COMPOSIO_USER_ID", "user_123")
|
|
101
|
+
s = c.create(user_id=user_id)
|
|
102
|
+
toolkits_info = s.toolkits()
|
|
103
|
+
tk = next((t for t in toolkits_info.items if t.slug == "gmail"), None)
|
|
104
|
+
return bool(tk and tk.connection and tk.connection.is_active)
|
|
105
|
+
except Exception:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
def sync(self) -> dict:
|
|
109
|
+
sync_gmail()
|
|
110
|
+
return {"status": "success"}
|
|
111
|
+
|
|
112
|
+
def health(self) -> tuple[bool, str]:
|
|
113
|
+
if self.authenticate():
|
|
114
|
+
return True, "Connected"
|
|
115
|
+
return False, "Not connected"
|
|
116
|
+
|
connectors/notion.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from composio import Composio
|
|
4
|
+
from models.memory import RepositoryDocument
|
|
5
|
+
from storage.db import insert_repository_document, get_connection
|
|
6
|
+
|
|
7
|
+
def get_page_title(page: dict) -> str:
|
|
8
|
+
properties = page.get("properties", {}) or {}
|
|
9
|
+
# Title is usually in 'title' field or 'Name' field
|
|
10
|
+
title_text = ""
|
|
11
|
+
for field_name in ["title", "Name", "name"]:
|
|
12
|
+
prop = properties.get(field_name)
|
|
13
|
+
if isinstance(prop, dict):
|
|
14
|
+
title_list = prop.get("title") or prop.get("rich_text") or []
|
|
15
|
+
if isinstance(title_list, list) and title_list:
|
|
16
|
+
title_text = "".join([t.get("plain_text", "") for t in title_list if isinstance(t, dict)])
|
|
17
|
+
if title_text:
|
|
18
|
+
break
|
|
19
|
+
if not title_text:
|
|
20
|
+
title_text = f"Notion Page {page.get('id')}"
|
|
21
|
+
return title_text
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
|
|
25
|
+
def sync_notion():
|
|
26
|
+
try:
|
|
27
|
+
try:
|
|
28
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
print("Syncing Notion...\n")
|
|
33
|
+
c = Composio()
|
|
34
|
+
user_id = os.getenv("COMPOSIO_USER_ID", "user_123")
|
|
35
|
+
s = c.create(user_id=user_id)
|
|
36
|
+
|
|
37
|
+
# Verify Notion toolkit is active
|
|
38
|
+
toolkits_info = s.toolkits()
|
|
39
|
+
notion_tk = next((t for t in toolkits_info.items if t.slug == "notion"), None)
|
|
40
|
+
if not notion_tk or not (notion_tk.connection and notion_tk.connection.is_active):
|
|
41
|
+
print("Notion connection not active.")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Fetch page list via Composio
|
|
45
|
+
resp = s.execute(tool_slug="notion_search_notion_page", arguments={"query": ""})
|
|
46
|
+
if resp.error or not resp.data:
|
|
47
|
+
print("No Notion pages found.")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
resp_data = resp.data
|
|
51
|
+
if "response_data" in resp_data:
|
|
52
|
+
resp_data = resp_data["response_data"]
|
|
53
|
+
|
|
54
|
+
pages = resp_data.get("results") or []
|
|
55
|
+
if not pages:
|
|
56
|
+
print("No Notion pages found in results.")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
print(f"Found {len(pages)} Notion pages")
|
|
60
|
+
|
|
61
|
+
conn = get_connection()
|
|
62
|
+
cursor = conn.cursor()
|
|
63
|
+
|
|
64
|
+
synced_count = 0
|
|
65
|
+
skipped_count = 0
|
|
66
|
+
|
|
67
|
+
for page in pages:
|
|
68
|
+
if not isinstance(page, dict):
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
page_id = page.get("id")
|
|
72
|
+
if not page_id:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
title = get_page_title(page)
|
|
76
|
+
last_edited_time = page.get("last_edited_time") or ""
|
|
77
|
+
|
|
78
|
+
# Incremental check: check if already in DB with same last_edited_time
|
|
79
|
+
cursor.execute(
|
|
80
|
+
"SELECT content, synced_at FROM repository_documents WHERE repo_name = 'Notion' AND file_name = ?",
|
|
81
|
+
(title,)
|
|
82
|
+
)
|
|
83
|
+
row = cursor.fetchone()
|
|
84
|
+
if row:
|
|
85
|
+
stored_synced_at = row[1]
|
|
86
|
+
# If we have stored it and it wasn't edited since our last sync, skip it
|
|
87
|
+
if stored_synced_at and last_edited_time and stored_synced_at >= last_edited_time:
|
|
88
|
+
skipped_count += 1
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
# Fetch page content
|
|
92
|
+
try:
|
|
93
|
+
print(f"Syncing page: {title}")
|
|
94
|
+
resp_content = s.execute(
|
|
95
|
+
tool_slug="notion_get_page_markdown",
|
|
96
|
+
arguments={"page_id": page_id}
|
|
97
|
+
)
|
|
98
|
+
if resp_content and not resp_content.error and resp_content.data:
|
|
99
|
+
content_data = resp_content.data
|
|
100
|
+
if "response_data" in content_data:
|
|
101
|
+
content_data = content_data["response_data"]
|
|
102
|
+
|
|
103
|
+
markdown = content_data.get("markdown") or ""
|
|
104
|
+
if markdown.strip():
|
|
105
|
+
# Save to database
|
|
106
|
+
doc = RepositoryDocument(
|
|
107
|
+
repo_name="Notion",
|
|
108
|
+
file_name=title,
|
|
109
|
+
content=markdown,
|
|
110
|
+
source="notion_get_page_markdown",
|
|
111
|
+
synced_at=datetime.now().isoformat()
|
|
112
|
+
)
|
|
113
|
+
insert_repository_document(doc)
|
|
114
|
+
synced_count += 1
|
|
115
|
+
else:
|
|
116
|
+
print(f"Page '{title}' content was empty.")
|
|
117
|
+
else:
|
|
118
|
+
print(f"Failed to fetch content for page: {title}")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
print(f"Error fetching page content for '{title}': {e}")
|
|
121
|
+
|
|
122
|
+
conn.close()
|
|
123
|
+
print(f"Notion Sync Complete: {synced_count} pages synced, {skipped_count} pages skipped.")
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Error during Notion sync: {e}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
from connectors.base import BaseConnector
|
|
130
|
+
from connectors.registry import register
|
|
131
|
+
|
|
132
|
+
@register
|
|
133
|
+
class NotionConnector(BaseConnector):
|
|
134
|
+
name = "Notion"
|
|
135
|
+
slug = "notion"
|
|
136
|
+
|
|
137
|
+
def authenticate(self) -> bool:
|
|
138
|
+
try:
|
|
139
|
+
c = Composio()
|
|
140
|
+
user_id = os.getenv("COMPOSIO_USER_ID", "user_123")
|
|
141
|
+
s = c.create(user_id=user_id)
|
|
142
|
+
toolkits_info = s.toolkits()
|
|
143
|
+
tk = next((t for t in toolkits_info.items if t.slug == "notion"), None)
|
|
144
|
+
return bool(tk and tk.connection and tk.connection.is_active)
|
|
145
|
+
except Exception:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
def sync(self) -> dict:
|
|
149
|
+
sync_notion()
|
|
150
|
+
return {"status": "success"}
|
|
151
|
+
|
|
152
|
+
def health(self) -> tuple[bool, str]:
|
|
153
|
+
if self.authenticate():
|
|
154
|
+
return True, "Connected"
|
|
155
|
+
return False, "Not connected"
|
|
156
|
+
|
connectors/registry.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connector registry.
|
|
3
|
+
|
|
4
|
+
Discovers and manages available connectors. In Phase 1 this wraps
|
|
5
|
+
the existing sync functions. Future connectors simply subclass
|
|
6
|
+
BaseConnector and are auto-discovered.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from connectors.base import BaseConnector
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("connectors.registry")
|
|
13
|
+
|
|
14
|
+
# Registry of known connector classes
|
|
15
|
+
_connector_classes: list[type[BaseConnector]] = []
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register(cls: type[BaseConnector]):
|
|
19
|
+
"""Register a connector class."""
|
|
20
|
+
if cls not in _connector_classes:
|
|
21
|
+
_connector_classes.append(cls)
|
|
22
|
+
return cls
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def discover_connectors() -> list[BaseConnector]:
|
|
26
|
+
"""Discover and instantiate all registered connectors.
|
|
27
|
+
|
|
28
|
+
Imports connector modules to trigger registration, then
|
|
29
|
+
returns instances of all registered connector classes.
|
|
30
|
+
"""
|
|
31
|
+
# Import connector modules to trigger @register decorators
|
|
32
|
+
try:
|
|
33
|
+
import connectors.github # noqa: F401
|
|
34
|
+
except ImportError:
|
|
35
|
+
logger.warning("Could not import connectors.github")
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import connectors.gmail # noqa: F401
|
|
39
|
+
except ImportError:
|
|
40
|
+
logger.warning("Could not import connectors.gmail")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
import connectors.notion # noqa: F401
|
|
44
|
+
except ImportError:
|
|
45
|
+
logger.warning("Could not import connectors.notion")
|
|
46
|
+
|
|
47
|
+
return [cls() for cls in _connector_classes]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_connector(name: str) -> BaseConnector | None:
|
|
51
|
+
"""Return a connector instance by name."""
|
|
52
|
+
for connector in discover_connectors():
|
|
53
|
+
if connector.name.lower() == name.lower() or connector.slug == name.lower():
|
|
54
|
+
return connector
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def list_connectors() -> list[str]:
|
|
59
|
+
"""Return names of all registered connectors."""
|
|
60
|
+
return [cls.name for cls in _connector_classes]
|
core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# core package
|
core/chunker.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from storage.db import (
|
|
3
|
+
get_all_repositories,
|
|
4
|
+
get_all_documents,
|
|
5
|
+
get_all_emails,
|
|
6
|
+
insert_document_chunk,
|
|
7
|
+
clear_document_chunks
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
def chunk_text(text: str, chunk_size: int = 800, overlap: int = 120) -> list:
|
|
11
|
+
if not text:
|
|
12
|
+
return []
|
|
13
|
+
chunks = []
|
|
14
|
+
start = 0
|
|
15
|
+
while start < len(text):
|
|
16
|
+
end = start + chunk_size
|
|
17
|
+
chunk = text[start:end]
|
|
18
|
+
chunks.append(chunk)
|
|
19
|
+
if end >= len(text):
|
|
20
|
+
break
|
|
21
|
+
start += chunk_size - overlap
|
|
22
|
+
return chunks
|
|
23
|
+
|
|
24
|
+
def generate_and_save_chunks(repo_names=None):
|
|
25
|
+
from storage.db import get_connection
|
|
26
|
+
created_at = datetime.datetime.now().isoformat()
|
|
27
|
+
|
|
28
|
+
conn = get_connection()
|
|
29
|
+
cursor = conn.cursor()
|
|
30
|
+
|
|
31
|
+
if repo_names is None:
|
|
32
|
+
clear_document_chunks()
|
|
33
|
+
|
|
34
|
+
# 1. Repositories (Metadata Chunks for identity search boosting)
|
|
35
|
+
repos = get_all_repositories()
|
|
36
|
+
for repo in repos:
|
|
37
|
+
repo_text = f"Repository: {repo['repo_name']}\nDescription: {repo['description'] or ''}\nLanguage: {repo['language'] or ''}"
|
|
38
|
+
# Insert repository metadata as a distinct source type to avoid mixing with document content
|
|
39
|
+
insert_document_chunk(
|
|
40
|
+
repository_name=repo["repo_name"],
|
|
41
|
+
document_name="metadata",
|
|
42
|
+
source_type="repository_metadata",
|
|
43
|
+
chunk_text=repo_text,
|
|
44
|
+
chunk_index=0,
|
|
45
|
+
created_at=created_at
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# 2. Repository Documents
|
|
49
|
+
docs = get_all_documents()
|
|
50
|
+
for doc in docs:
|
|
51
|
+
content = doc["content"] or ""
|
|
52
|
+
chunks = chunk_text(content)
|
|
53
|
+
for idx, chunk in enumerate(chunks):
|
|
54
|
+
insert_document_chunk(
|
|
55
|
+
repository_name=doc["repo_name"],
|
|
56
|
+
document_name=doc["file_name"],
|
|
57
|
+
source_type="document",
|
|
58
|
+
chunk_text=chunk,
|
|
59
|
+
chunk_index=idx,
|
|
60
|
+
created_at=created_at
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 3. Emails
|
|
64
|
+
emails = get_all_emails()
|
|
65
|
+
for email in emails:
|
|
66
|
+
email_text = f"Subject: {email['subject']}\nFrom: {email['sender']}\nDate: {email['received_at']}\nContent: {email['snippet']}"
|
|
67
|
+
chunks = chunk_text(email_text)
|
|
68
|
+
for idx, chunk in enumerate(chunks):
|
|
69
|
+
insert_document_chunk(
|
|
70
|
+
repository_name=None,
|
|
71
|
+
document_name=email["subject"] or email["message_id"],
|
|
72
|
+
source_type="email",
|
|
73
|
+
chunk_text=chunk,
|
|
74
|
+
chunk_index=idx,
|
|
75
|
+
created_at=created_at
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
# Incremental sync for specific repositories or emails
|
|
79
|
+
for r_name in repo_names:
|
|
80
|
+
if r_name == "__emails__":
|
|
81
|
+
# Delete existing email chunks
|
|
82
|
+
cursor.execute("DELETE FROM document_chunks WHERE source_type = 'email'")
|
|
83
|
+
conn.commit()
|
|
84
|
+
# Insert email chunks
|
|
85
|
+
emails = get_all_emails()
|
|
86
|
+
for email in emails:
|
|
87
|
+
email_text = f"Subject: {email['subject']}\nFrom: {email['sender']}\nDate: {email['received_at']}\nContent: {email['snippet']}"
|
|
88
|
+
chunks = chunk_text(email_text)
|
|
89
|
+
for idx, chunk in enumerate(chunks):
|
|
90
|
+
insert_document_chunk(
|
|
91
|
+
repository_name=None,
|
|
92
|
+
document_name=email["subject"] or email["message_id"],
|
|
93
|
+
source_type="email",
|
|
94
|
+
chunk_text=chunk,
|
|
95
|
+
chunk_index=idx,
|
|
96
|
+
created_at=created_at
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
# Delete existing metadata and doc chunks for this repo
|
|
100
|
+
cursor.execute(
|
|
101
|
+
"DELETE FROM document_chunks WHERE repository_name = ? AND source_type IN ('document', 'repository_metadata')",
|
|
102
|
+
(r_name,)
|
|
103
|
+
)
|
|
104
|
+
conn.commit()
|
|
105
|
+
|
|
106
|
+
# Insert repo metadata chunk
|
|
107
|
+
cursor.execute("SELECT repo_name, description, language FROM repositories WHERE repo_name = ?", (r_name,))
|
|
108
|
+
repo_row = cursor.fetchone()
|
|
109
|
+
if repo_row:
|
|
110
|
+
repo_text = f"Repository: {repo_row[0]}\nDescription: {repo_row[1] or ''}\nLanguage: {repo_row[2] or ''}"
|
|
111
|
+
insert_document_chunk(
|
|
112
|
+
repository_name=repo_row[0],
|
|
113
|
+
document_name="metadata",
|
|
114
|
+
source_type="repository_metadata",
|
|
115
|
+
chunk_text=repo_text,
|
|
116
|
+
chunk_index=0,
|
|
117
|
+
created_at=created_at
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Insert repo document chunks
|
|
121
|
+
cursor.execute("SELECT repo_name, file_name, content FROM repository_documents WHERE repo_name = ?", (r_name,))
|
|
122
|
+
doc_rows = cursor.fetchall()
|
|
123
|
+
for d_row in doc_rows:
|
|
124
|
+
content = d_row[2] or ""
|
|
125
|
+
chunks = chunk_text(content)
|
|
126
|
+
for idx, chunk in enumerate(chunks):
|
|
127
|
+
insert_document_chunk(
|
|
128
|
+
repository_name=d_row[0],
|
|
129
|
+
document_name=d_row[1],
|
|
130
|
+
source_type="document",
|
|
131
|
+
chunk_text=chunk,
|
|
132
|
+
chunk_index=idx,
|
|
133
|
+
created_at=created_at
|
|
134
|
+
)
|
|
135
|
+
conn.close()
|
|
136
|
+
|