omni-cortex 1.1.0__tar.gz → 1.3.0__tar.gz
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.
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/PKG-INFO +1 -1
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/chat_service.py +40 -22
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/database.py +208 -0
- omni_cortex-1.3.0/dashboard/backend/image_service.py +543 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/main.py +180 -1
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/models.py +45 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/uv.lock +414 -1
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/__init__.py +1 -1
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/dashboard.py +48 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/migrations.py +13 -4
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/activity.py +29 -2
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/activities.py +89 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/pyproject.toml +1 -1
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/.gitignore +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/LICENSE +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/README.md +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/logging_config.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/project_config.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/project_scanner.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/pyproject.toml +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/websocket_manager.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/hooks/post_tool_use.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/hooks/pre_tool_use.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/hooks/stop.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/hooks/subagent_stop.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/categorization/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/categorization/auto_tags.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/categorization/auto_type.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/config.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/connection.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/schema.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/sync.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/decay/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/decay/importance.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/embeddings/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/embeddings/local.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/agent.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/memory.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/relationship.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/session.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/resources/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/hybrid.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/keyword.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/ranking.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/semantic.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/server.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/setup.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/memories.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/sessions.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/utilities.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/__init__.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/formatting.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/ids.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/timestamps.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/truncation.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/scripts/import_ken_memories.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/scripts/populate_session_data.py +0 -0
- {omni_cortex-1.1.0 → omni_cortex-1.3.0}/scripts/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omni-cortex
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Give Claude Code a perfect memory - auto-logs everything, searches smartly, and gets smarter over time
|
|
5
5
|
Project-URL: Homepage, https://github.com/AllCytes/Omni-Cortex
|
|
6
6
|
Project-URL: Repository, https://github.com/AllCytes/Omni-Cortex
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import Optional, AsyncGenerator, Any
|
|
5
5
|
|
|
6
|
-
import google.generativeai as genai
|
|
7
6
|
from dotenv import load_dotenv
|
|
8
7
|
|
|
9
8
|
from database import search_memories, get_memories, create_memory
|
|
@@ -14,21 +13,30 @@ load_dotenv()
|
|
|
14
13
|
|
|
15
14
|
# Configure Gemini
|
|
16
15
|
_api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
|
17
|
-
|
|
16
|
+
_client = None
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
def
|
|
21
|
-
"""Get or initialize the Gemini
|
|
22
|
-
global
|
|
23
|
-
if
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
def get_client():
|
|
20
|
+
"""Get or initialize the Gemini client."""
|
|
21
|
+
global _client
|
|
22
|
+
if _client is None and _api_key:
|
|
23
|
+
try:
|
|
24
|
+
from google import genai
|
|
25
|
+
_client = genai.Client(api_key=_api_key)
|
|
26
|
+
except ImportError:
|
|
27
|
+
return None
|
|
28
|
+
return _client
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
def is_available() -> bool:
|
|
30
32
|
"""Check if the chat service is available."""
|
|
31
|
-
|
|
33
|
+
if not _api_key:
|
|
34
|
+
return False
|
|
35
|
+
try:
|
|
36
|
+
from google import genai
|
|
37
|
+
return True
|
|
38
|
+
except ImportError:
|
|
39
|
+
return False
|
|
32
40
|
|
|
33
41
|
|
|
34
42
|
def _build_prompt(question: str, context_str: str) -> str:
|
|
@@ -111,11 +119,11 @@ async def stream_ask_about_memories(
|
|
|
111
119
|
}
|
|
112
120
|
return
|
|
113
121
|
|
|
114
|
-
|
|
115
|
-
if not
|
|
122
|
+
client = get_client()
|
|
123
|
+
if not client:
|
|
116
124
|
yield {
|
|
117
125
|
"type": "error",
|
|
118
|
-
"data": "Failed to initialize Gemini
|
|
126
|
+
"data": "Failed to initialize Gemini client.",
|
|
119
127
|
}
|
|
120
128
|
return
|
|
121
129
|
|
|
@@ -146,7 +154,11 @@ async def stream_ask_about_memories(
|
|
|
146
154
|
prompt = _build_prompt(question, context_str)
|
|
147
155
|
|
|
148
156
|
try:
|
|
149
|
-
|
|
157
|
+
# Use streaming with the new google.genai client
|
|
158
|
+
response = client.models.generate_content_stream(
|
|
159
|
+
model="gemini-2.0-flash",
|
|
160
|
+
contents=prompt,
|
|
161
|
+
)
|
|
150
162
|
|
|
151
163
|
for chunk in response:
|
|
152
164
|
if chunk.text:
|
|
@@ -196,15 +208,18 @@ async def save_conversation(
|
|
|
196
208
|
|
|
197
209
|
# Generate summary using Gemini if available
|
|
198
210
|
summary = "Chat conversation"
|
|
199
|
-
|
|
200
|
-
if
|
|
211
|
+
client = get_client()
|
|
212
|
+
if client:
|
|
201
213
|
try:
|
|
202
214
|
summary_prompt = f"""Summarize this conversation in one concise sentence (max 100 chars):
|
|
203
215
|
|
|
204
216
|
{content[:2000]}
|
|
205
217
|
|
|
206
218
|
Summary:"""
|
|
207
|
-
response =
|
|
219
|
+
response = client.models.generate_content(
|
|
220
|
+
model="gemini-2.0-flash",
|
|
221
|
+
contents=summary_prompt,
|
|
222
|
+
)
|
|
208
223
|
summary = response.text.strip()[:100]
|
|
209
224
|
except Exception:
|
|
210
225
|
# Use fallback summary
|
|
@@ -254,12 +269,12 @@ async def ask_about_memories(
|
|
|
254
269
|
"error": "api_key_missing",
|
|
255
270
|
}
|
|
256
271
|
|
|
257
|
-
|
|
258
|
-
if not
|
|
272
|
+
client = get_client()
|
|
273
|
+
if not client:
|
|
259
274
|
return {
|
|
260
|
-
"answer": "Failed to initialize Gemini
|
|
275
|
+
"answer": "Failed to initialize Gemini client.",
|
|
261
276
|
"sources": [],
|
|
262
|
-
"error": "
|
|
277
|
+
"error": "client_init_failed",
|
|
263
278
|
}
|
|
264
279
|
|
|
265
280
|
context_str, sources = _get_memories_and_sources(db_path, question, max_memories)
|
|
@@ -274,7 +289,10 @@ async def ask_about_memories(
|
|
|
274
289
|
prompt = _build_prompt(question, context_str)
|
|
275
290
|
|
|
276
291
|
try:
|
|
277
|
-
response =
|
|
292
|
+
response = client.models.generate_content(
|
|
293
|
+
model="gemini-2.0-flash",
|
|
294
|
+
contents=prompt,
|
|
295
|
+
)
|
|
278
296
|
answer = response.text
|
|
279
297
|
except Exception as e:
|
|
280
298
|
return {
|
|
@@ -729,6 +729,214 @@ def get_relationship_graph(db_path: str, center_id: Optional[str] = None, depth:
|
|
|
729
729
|
return {"nodes": list(nodes.values()), "edges": edges}
|
|
730
730
|
|
|
731
731
|
|
|
732
|
+
# --- Command Analytics Functions ---
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def get_command_usage(db_path: str, scope: Optional[str] = None, days: int = 30) -> list[dict]:
|
|
736
|
+
"""Get slash command usage statistics aggregated by command_name.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
db_path: Path to database
|
|
740
|
+
scope: Filter by scope ('universal', 'project', or None for all)
|
|
741
|
+
days: Number of days to look back
|
|
742
|
+
|
|
743
|
+
Returns:
|
|
744
|
+
List of command usage entries with counts and success rates
|
|
745
|
+
"""
|
|
746
|
+
conn = get_connection(db_path)
|
|
747
|
+
|
|
748
|
+
# Check if command_name column exists
|
|
749
|
+
columns = conn.execute("PRAGMA table_info(activities)").fetchall()
|
|
750
|
+
column_names = [col[1] for col in columns]
|
|
751
|
+
if "command_name" not in column_names:
|
|
752
|
+
conn.close()
|
|
753
|
+
return []
|
|
754
|
+
|
|
755
|
+
query = """
|
|
756
|
+
SELECT
|
|
757
|
+
command_name,
|
|
758
|
+
command_scope,
|
|
759
|
+
COUNT(*) as count,
|
|
760
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) as success_rate,
|
|
761
|
+
AVG(duration_ms) as avg_duration_ms
|
|
762
|
+
FROM activities
|
|
763
|
+
WHERE command_name IS NOT NULL
|
|
764
|
+
AND command_name != ''
|
|
765
|
+
AND timestamp >= date('now', ?)
|
|
766
|
+
"""
|
|
767
|
+
params = [f'-{days} days']
|
|
768
|
+
|
|
769
|
+
if scope:
|
|
770
|
+
query += " AND command_scope = ?"
|
|
771
|
+
params.append(scope)
|
|
772
|
+
|
|
773
|
+
query += " GROUP BY command_name, command_scope ORDER BY count DESC"
|
|
774
|
+
|
|
775
|
+
cursor = conn.execute(query, params)
|
|
776
|
+
result = [
|
|
777
|
+
{
|
|
778
|
+
"command_name": row["command_name"],
|
|
779
|
+
"command_scope": row["command_scope"] or "unknown",
|
|
780
|
+
"count": row["count"],
|
|
781
|
+
"success_rate": round(row["success_rate"], 2) if row["success_rate"] else 1.0,
|
|
782
|
+
"avg_duration_ms": round(row["avg_duration_ms"]) if row["avg_duration_ms"] else None,
|
|
783
|
+
}
|
|
784
|
+
for row in cursor.fetchall()
|
|
785
|
+
]
|
|
786
|
+
conn.close()
|
|
787
|
+
return result
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def get_skill_usage(db_path: str, scope: Optional[str] = None, days: int = 30) -> list[dict]:
|
|
791
|
+
"""Get skill usage statistics aggregated by skill_name.
|
|
792
|
+
|
|
793
|
+
Args:
|
|
794
|
+
db_path: Path to database
|
|
795
|
+
scope: Filter by scope ('universal', 'project', or None for all)
|
|
796
|
+
days: Number of days to look back
|
|
797
|
+
|
|
798
|
+
Returns:
|
|
799
|
+
List of skill usage entries with counts and success rates
|
|
800
|
+
"""
|
|
801
|
+
conn = get_connection(db_path)
|
|
802
|
+
|
|
803
|
+
# Check if skill_name column exists
|
|
804
|
+
columns = conn.execute("PRAGMA table_info(activities)").fetchall()
|
|
805
|
+
column_names = [col[1] for col in columns]
|
|
806
|
+
if "skill_name" not in column_names:
|
|
807
|
+
conn.close()
|
|
808
|
+
return []
|
|
809
|
+
|
|
810
|
+
query = """
|
|
811
|
+
SELECT
|
|
812
|
+
skill_name,
|
|
813
|
+
command_scope,
|
|
814
|
+
COUNT(*) as count,
|
|
815
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) as success_rate,
|
|
816
|
+
AVG(duration_ms) as avg_duration_ms
|
|
817
|
+
FROM activities
|
|
818
|
+
WHERE skill_name IS NOT NULL
|
|
819
|
+
AND skill_name != ''
|
|
820
|
+
AND timestamp >= date('now', ?)
|
|
821
|
+
"""
|
|
822
|
+
params = [f'-{days} days']
|
|
823
|
+
|
|
824
|
+
if scope:
|
|
825
|
+
query += " AND command_scope = ?"
|
|
826
|
+
params.append(scope)
|
|
827
|
+
|
|
828
|
+
query += " GROUP BY skill_name, command_scope ORDER BY count DESC"
|
|
829
|
+
|
|
830
|
+
cursor = conn.execute(query, params)
|
|
831
|
+
result = [
|
|
832
|
+
{
|
|
833
|
+
"skill_name": row["skill_name"],
|
|
834
|
+
"skill_scope": row["command_scope"] or "unknown",
|
|
835
|
+
"count": row["count"],
|
|
836
|
+
"success_rate": round(row["success_rate"], 2) if row["success_rate"] else 1.0,
|
|
837
|
+
"avg_duration_ms": round(row["avg_duration_ms"]) if row["avg_duration_ms"] else None,
|
|
838
|
+
}
|
|
839
|
+
for row in cursor.fetchall()
|
|
840
|
+
]
|
|
841
|
+
conn.close()
|
|
842
|
+
return result
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def get_mcp_usage(db_path: str, days: int = 30) -> list[dict]:
|
|
846
|
+
"""Get MCP server usage statistics.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
db_path: Path to database
|
|
850
|
+
days: Number of days to look back
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
List of MCP server usage entries with tool counts and call totals
|
|
854
|
+
"""
|
|
855
|
+
conn = get_connection(db_path)
|
|
856
|
+
|
|
857
|
+
# Check if mcp_server column exists
|
|
858
|
+
columns = conn.execute("PRAGMA table_info(activities)").fetchall()
|
|
859
|
+
column_names = [col[1] for col in columns]
|
|
860
|
+
if "mcp_server" not in column_names:
|
|
861
|
+
conn.close()
|
|
862
|
+
return []
|
|
863
|
+
|
|
864
|
+
query = """
|
|
865
|
+
SELECT
|
|
866
|
+
mcp_server,
|
|
867
|
+
COUNT(DISTINCT tool_name) as tool_count,
|
|
868
|
+
COUNT(*) as total_calls,
|
|
869
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) as success_rate
|
|
870
|
+
FROM activities
|
|
871
|
+
WHERE mcp_server IS NOT NULL
|
|
872
|
+
AND mcp_server != ''
|
|
873
|
+
AND timestamp >= date('now', ?)
|
|
874
|
+
GROUP BY mcp_server
|
|
875
|
+
ORDER BY total_calls DESC
|
|
876
|
+
"""
|
|
877
|
+
cursor = conn.execute(query, (f'-{days} days',))
|
|
878
|
+
result = [
|
|
879
|
+
{
|
|
880
|
+
"mcp_server": row["mcp_server"],
|
|
881
|
+
"tool_count": row["tool_count"],
|
|
882
|
+
"total_calls": row["total_calls"],
|
|
883
|
+
"success_rate": round(row["success_rate"], 2) if row["success_rate"] else 1.0,
|
|
884
|
+
}
|
|
885
|
+
for row in cursor.fetchall()
|
|
886
|
+
]
|
|
887
|
+
conn.close()
|
|
888
|
+
return result
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def get_activity_detail(db_path: str, activity_id: str) -> Optional[dict]:
|
|
892
|
+
"""Get full activity details including complete input/output.
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
db_path: Path to database
|
|
896
|
+
activity_id: Activity ID
|
|
897
|
+
|
|
898
|
+
Returns:
|
|
899
|
+
Full activity details or None if not found
|
|
900
|
+
"""
|
|
901
|
+
conn = get_connection(db_path)
|
|
902
|
+
cursor = conn.execute("SELECT * FROM activities WHERE id = ?", (activity_id,))
|
|
903
|
+
row = cursor.fetchone()
|
|
904
|
+
|
|
905
|
+
if not row:
|
|
906
|
+
conn.close()
|
|
907
|
+
return None
|
|
908
|
+
|
|
909
|
+
# Get column names for safe access
|
|
910
|
+
column_names = [description[0] for description in cursor.description]
|
|
911
|
+
|
|
912
|
+
result = {
|
|
913
|
+
"id": row["id"],
|
|
914
|
+
"session_id": row["session_id"],
|
|
915
|
+
"event_type": row["event_type"],
|
|
916
|
+
"tool_name": row["tool_name"],
|
|
917
|
+
"tool_input_full": row["tool_input"],
|
|
918
|
+
"tool_output_full": row["tool_output"],
|
|
919
|
+
"success": bool(row["success"]),
|
|
920
|
+
"error_message": row["error_message"],
|
|
921
|
+
"duration_ms": row["duration_ms"],
|
|
922
|
+
"file_path": row["file_path"],
|
|
923
|
+
"timestamp": row["timestamp"],
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
# Add command analytics fields if they exist
|
|
927
|
+
if "command_name" in column_names:
|
|
928
|
+
result["command_name"] = row["command_name"]
|
|
929
|
+
if "command_scope" in column_names:
|
|
930
|
+
result["command_scope"] = row["command_scope"]
|
|
931
|
+
if "mcp_server" in column_names:
|
|
932
|
+
result["mcp_server"] = row["mcp_server"]
|
|
933
|
+
if "skill_name" in column_names:
|
|
934
|
+
result["skill_name"] = row["skill_name"]
|
|
935
|
+
|
|
936
|
+
conn.close()
|
|
937
|
+
return result
|
|
938
|
+
|
|
939
|
+
|
|
732
940
|
def create_memory(
|
|
733
941
|
db_path: str,
|
|
734
942
|
content: str,
|