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.
Files changed (62) hide show
  1. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/PKG-INFO +1 -1
  2. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/chat_service.py +40 -22
  3. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/database.py +208 -0
  4. omni_cortex-1.3.0/dashboard/backend/image_service.py +543 -0
  5. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/main.py +180 -1
  6. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/models.py +45 -0
  7. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/uv.lock +414 -1
  8. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/__init__.py +1 -1
  9. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/dashboard.py +48 -0
  10. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/migrations.py +13 -4
  11. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/activity.py +29 -2
  12. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/activities.py +89 -0
  13. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/pyproject.toml +1 -1
  14. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/.gitignore +0 -0
  15. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/LICENSE +0 -0
  16. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/README.md +0 -0
  17. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/logging_config.py +0 -0
  18. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/project_config.py +0 -0
  19. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/project_scanner.py +0 -0
  20. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/pyproject.toml +0 -0
  21. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/dashboard/backend/websocket_manager.py +0 -0
  22. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/hooks/post_tool_use.py +0 -0
  23. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/hooks/pre_tool_use.py +0 -0
  24. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/hooks/stop.py +0 -0
  25. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/hooks/subagent_stop.py +0 -0
  26. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/categorization/__init__.py +0 -0
  27. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/categorization/auto_tags.py +0 -0
  28. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/categorization/auto_type.py +0 -0
  29. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/config.py +0 -0
  30. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/__init__.py +0 -0
  31. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/connection.py +0 -0
  32. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/schema.py +0 -0
  33. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/database/sync.py +0 -0
  34. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/decay/__init__.py +0 -0
  35. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/decay/importance.py +0 -0
  36. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/embeddings/__init__.py +0 -0
  37. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/embeddings/local.py +0 -0
  38. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/__init__.py +0 -0
  39. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/agent.py +0 -0
  40. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/memory.py +0 -0
  41. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/relationship.py +0 -0
  42. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/models/session.py +0 -0
  43. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/resources/__init__.py +0 -0
  44. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/__init__.py +0 -0
  45. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/hybrid.py +0 -0
  46. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/keyword.py +0 -0
  47. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/ranking.py +0 -0
  48. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/search/semantic.py +0 -0
  49. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/server.py +0 -0
  50. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/setup.py +0 -0
  51. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/__init__.py +0 -0
  52. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/memories.py +0 -0
  53. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/sessions.py +0 -0
  54. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/tools/utilities.py +0 -0
  55. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/__init__.py +0 -0
  56. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/formatting.py +0 -0
  57. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/ids.py +0 -0
  58. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/timestamps.py +0 -0
  59. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/omni_cortex/utils/truncation.py +0 -0
  60. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/scripts/import_ken_memories.py +0 -0
  61. {omni_cortex-1.1.0 → omni_cortex-1.3.0}/scripts/populate_session_data.py +0 -0
  62. {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.1.0
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
- _model: Optional[genai.GenerativeModel] = None
16
+ _client = None
18
17
 
19
18
 
20
- def get_model() -> Optional[genai.GenerativeModel]:
21
- """Get or initialize the Gemini model."""
22
- global _model
23
- if _model is None and _api_key:
24
- genai.configure(api_key=_api_key)
25
- _model = genai.GenerativeModel("gemini-3-flash-preview")
26
- return _model
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
- return _api_key is not None
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
- model = get_model()
115
- if not model:
122
+ client = get_client()
123
+ if not client:
116
124
  yield {
117
125
  "type": "error",
118
- "data": "Failed to initialize Gemini model.",
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
- response = model.generate_content(prompt, stream=True)
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
- model = get_model()
200
- if model:
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 = model.generate_content(summary_prompt)
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
- model = get_model()
258
- if not model:
272
+ client = get_client()
273
+ if not client:
259
274
  return {
260
- "answer": "Failed to initialize Gemini model.",
275
+ "answer": "Failed to initialize Gemini client.",
261
276
  "sources": [],
262
- "error": "model_init_failed",
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 = model.generate_content(prompt)
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,