omni-cortex 1.17.0__py3-none-any.whl → 1.17.2__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.
Files changed (66) hide show
  1. omni_cortex/__init__.py +3 -0
  2. omni_cortex/categorization/__init__.py +9 -0
  3. omni_cortex/categorization/auto_tags.py +166 -0
  4. omni_cortex/categorization/auto_type.py +165 -0
  5. omni_cortex/config.py +141 -0
  6. omni_cortex/dashboard.py +232 -0
  7. omni_cortex/database/__init__.py +24 -0
  8. omni_cortex/database/connection.py +137 -0
  9. omni_cortex/database/migrations.py +210 -0
  10. omni_cortex/database/schema.py +212 -0
  11. omni_cortex/database/sync.py +421 -0
  12. omni_cortex/decay/__init__.py +7 -0
  13. omni_cortex/decay/importance.py +147 -0
  14. omni_cortex/embeddings/__init__.py +35 -0
  15. omni_cortex/embeddings/local.py +442 -0
  16. omni_cortex/models/__init__.py +20 -0
  17. omni_cortex/models/activity.py +265 -0
  18. omni_cortex/models/agent.py +144 -0
  19. omni_cortex/models/memory.py +395 -0
  20. omni_cortex/models/relationship.py +206 -0
  21. omni_cortex/models/session.py +290 -0
  22. omni_cortex/resources/__init__.py +1 -0
  23. omni_cortex/search/__init__.py +22 -0
  24. omni_cortex/search/hybrid.py +197 -0
  25. omni_cortex/search/keyword.py +204 -0
  26. omni_cortex/search/ranking.py +127 -0
  27. omni_cortex/search/semantic.py +232 -0
  28. omni_cortex/server.py +360 -0
  29. omni_cortex/setup.py +278 -0
  30. omni_cortex/tools/__init__.py +13 -0
  31. omni_cortex/tools/activities.py +453 -0
  32. omni_cortex/tools/memories.py +536 -0
  33. omni_cortex/tools/sessions.py +311 -0
  34. omni_cortex/tools/utilities.py +477 -0
  35. omni_cortex/utils/__init__.py +13 -0
  36. omni_cortex/utils/formatting.py +282 -0
  37. omni_cortex/utils/ids.py +72 -0
  38. omni_cortex/utils/timestamps.py +129 -0
  39. omni_cortex/utils/truncation.py +111 -0
  40. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/main.py +43 -13
  41. {omni_cortex-1.17.0.dist-info → omni_cortex-1.17.2.dist-info}/METADATA +1 -1
  42. omni_cortex-1.17.2.dist-info/RECORD +65 -0
  43. omni_cortex-1.17.0.dist-info/RECORD +0 -26
  44. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/.env.example +0 -0
  45. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/backfill_summaries.py +0 -0
  46. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/chat_service.py +0 -0
  47. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/database.py +0 -0
  48. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/image_service.py +0 -0
  49. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/logging_config.py +0 -0
  50. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/models.py +0 -0
  51. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/project_config.py +0 -0
  52. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/project_scanner.py +0 -0
  53. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/prompt_security.py +0 -0
  54. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/pyproject.toml +0 -0
  55. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/security.py +0 -0
  56. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/uv.lock +0 -0
  57. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/dashboard/backend/websocket_manager.py +0 -0
  58. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/hooks/post_tool_use.py +0 -0
  59. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/hooks/pre_tool_use.py +0 -0
  60. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/hooks/session_utils.py +0 -0
  61. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/hooks/stop.py +0 -0
  62. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/hooks/subagent_stop.py +0 -0
  63. {omni_cortex-1.17.0.data → omni_cortex-1.17.2.data}/data/share/omni-cortex/hooks/user_prompt.py +0 -0
  64. {omni_cortex-1.17.0.dist-info → omni_cortex-1.17.2.dist-info}/WHEEL +0 -0
  65. {omni_cortex-1.17.0.dist-info → omni_cortex-1.17.2.dist-info}/entry_points.txt +0 -0
  66. {omni_cortex-1.17.0.dist-info → omni_cortex-1.17.2.dist-info}/licenses/LICENSE +0 -0
omni_cortex/server.py ADDED
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env python3
2
+ """Omni Cortex MCP Server - Universal Memory System for Claude Code.
3
+
4
+ This server provides a dual-layer memory system combining:
5
+ - Activity logging (audit trail of all tool calls and decisions)
6
+ - Knowledge storage (distilled insights, solutions, and learnings)
7
+
8
+ Features:
9
+ - 15 tools across 4 categories: Activities, Memories, Sessions, Utilities
10
+ - Full-text search with FTS5
11
+ - Auto-categorization and smart tagging
12
+ - Multi-factor relevance ranking
13
+ - Session continuity ("Last time you were working on...")
14
+ - Importance decay over time
15
+ """
16
+
17
+ from contextlib import asynccontextmanager
18
+ from typing import AsyncGenerator
19
+
20
+ from mcp.server.fastmcp import FastMCP
21
+
22
+ from .database.connection import init_database, close_all_connections
23
+ from .tools.memories import register_memory_tools
24
+ from .tools.activities import register_activity_tools
25
+ from .tools.sessions import register_session_tools
26
+ from .tools.utilities import register_utility_tools
27
+
28
+
29
+ @asynccontextmanager
30
+ async def lifespan(mcp: FastMCP) -> AsyncGenerator[dict, None]:
31
+ """Manage server lifecycle - initialize and cleanup resources."""
32
+ # Initialize database on startup
33
+ try:
34
+ init_database()
35
+ init_database(is_global=True)
36
+ except Exception as e:
37
+ print(f"Warning: Failed to initialize database: {e}")
38
+
39
+ yield {}
40
+
41
+ # Cleanup on shutdown
42
+ close_all_connections()
43
+
44
+
45
+ # Create the MCP server
46
+ mcp = FastMCP(
47
+ "omni_cortex",
48
+ lifespan=lifespan,
49
+ )
50
+
51
+ # Register all tools
52
+ register_memory_tools(mcp)
53
+ register_activity_tools(mcp)
54
+ register_session_tools(mcp)
55
+ register_utility_tools(mcp)
56
+
57
+
58
+ # === MCP Resources ===
59
+
60
+ @mcp.resource("cortex://stats")
61
+ async def get_stats() -> str:
62
+ """Get statistics about the Cortex database."""
63
+ try:
64
+ conn = init_database()
65
+ cursor = conn.cursor()
66
+
67
+ stats = {}
68
+
69
+ # Count memories
70
+ cursor.execute("SELECT COUNT(*) FROM memories")
71
+ stats["total_memories"] = cursor.fetchone()[0]
72
+
73
+ # Count by type
74
+ cursor.execute("""
75
+ SELECT type, COUNT(*) as cnt
76
+ FROM memories
77
+ GROUP BY type
78
+ ORDER BY cnt DESC
79
+ """)
80
+ stats["memories_by_type"] = {row["type"]: row["cnt"] for row in cursor.fetchall()}
81
+
82
+ # Count by status
83
+ cursor.execute("""
84
+ SELECT status, COUNT(*) as cnt
85
+ FROM memories
86
+ GROUP BY status
87
+ """)
88
+ stats["memories_by_status"] = {row["status"]: row["cnt"] for row in cursor.fetchall()}
89
+
90
+ # Count activities
91
+ cursor.execute("SELECT COUNT(*) FROM activities")
92
+ stats["total_activities"] = cursor.fetchone()[0]
93
+
94
+ # Count sessions
95
+ cursor.execute("SELECT COUNT(*) FROM sessions")
96
+ stats["total_sessions"] = cursor.fetchone()[0]
97
+
98
+ import json
99
+ return json.dumps(stats, indent=2)
100
+
101
+ except Exception as e:
102
+ return f"Error getting stats: {e}"
103
+
104
+
105
+ @mcp.resource("cortex://types")
106
+ async def get_memory_types() -> str:
107
+ """Get available memory types with descriptions."""
108
+ types = {
109
+ "general": "General information or notes",
110
+ "warning": "Warnings, cautions, things to avoid",
111
+ "tip": "Tips, tricks, best practices",
112
+ "config": "Configuration, settings, environment variables",
113
+ "troubleshooting": "Problem solving, debugging guides",
114
+ "code": "Code snippets, functions, algorithms",
115
+ "error": "Errors, exceptions, failure cases",
116
+ "solution": "Solutions to problems, fixes",
117
+ "command": "CLI commands, terminal operations",
118
+ "concept": "Definitions, explanations, concepts",
119
+ "decision": "Decisions made, architectural choices",
120
+ }
121
+ import json
122
+ return json.dumps(types, indent=2)
123
+
124
+
125
+ @mcp.resource("cortex://config")
126
+ async def get_config() -> str:
127
+ """Get current Cortex configuration."""
128
+ from .config import load_config
129
+
130
+ config = load_config()
131
+ import json
132
+ return json.dumps({
133
+ "schema_version": config.schema_version,
134
+ "embedding_model": config.embedding_model,
135
+ "embedding_enabled": config.embedding_enabled,
136
+ "decay_rate_per_day": config.decay_rate_per_day,
137
+ "freshness_review_days": config.freshness_review_days,
138
+ "auto_provide_context": config.auto_provide_context,
139
+ "context_depth": config.context_depth,
140
+ "default_search_mode": config.default_search_mode,
141
+ "global_sync_enabled": config.global_sync_enabled,
142
+ }, indent=2)
143
+
144
+
145
+ @mcp.resource("cortex://tags")
146
+ async def get_tags() -> str:
147
+ """Get all tags used in memories with usage counts."""
148
+ try:
149
+ conn = init_database()
150
+ cursor = conn.cursor()
151
+
152
+ # Query all memories and extract tags
153
+ cursor.execute("SELECT tags FROM memories WHERE tags IS NOT NULL")
154
+
155
+ import json
156
+ tag_counts: dict[str, int] = {}
157
+
158
+ for row in cursor.fetchall():
159
+ tags_json = row["tags"]
160
+ if tags_json:
161
+ try:
162
+ tags = json.loads(tags_json)
163
+ for tag in tags:
164
+ tag_counts[tag] = tag_counts.get(tag, 0) + 1
165
+ except json.JSONDecodeError:
166
+ pass
167
+
168
+ # Sort by count descending
169
+ sorted_tags = sorted(tag_counts.items(), key=lambda x: x[1], reverse=True)
170
+
171
+ return json.dumps({
172
+ "total_unique_tags": len(sorted_tags),
173
+ "tags": [{"name": name, "count": count} for name, count in sorted_tags],
174
+ }, indent=2)
175
+
176
+ except Exception as e:
177
+ return f"Error getting tags: {e}"
178
+
179
+
180
+ @mcp.resource("cortex://sessions/recent")
181
+ async def get_recent_sessions_resource() -> str:
182
+ """Get recent sessions with summaries."""
183
+ try:
184
+ conn = init_database()
185
+ from .models.session import get_recent_sessions, get_session_summary
186
+
187
+ sessions = get_recent_sessions(conn, limit=10)
188
+
189
+ import json
190
+ result = []
191
+
192
+ for session in sessions:
193
+ session_data = {
194
+ "id": session.id,
195
+ "project_path": session.project_path,
196
+ "started_at": session.started_at,
197
+ "ended_at": session.ended_at,
198
+ "summary": session.summary,
199
+ }
200
+
201
+ # Get summary if available
202
+ summary = get_session_summary(conn, session.id)
203
+ if summary:
204
+ session_data["stats"] = {
205
+ "total_activities": summary.total_activities,
206
+ "memories_created": summary.total_memories_created,
207
+ "tools_used": summary.tools_used,
208
+ "key_learnings": summary.key_learnings,
209
+ }
210
+
211
+ result.append(session_data)
212
+
213
+ return json.dumps({
214
+ "total_sessions": len(result),
215
+ "sessions": result,
216
+ }, indent=2)
217
+
218
+ except Exception as e:
219
+ return f"Error getting recent sessions: {e}"
220
+
221
+
222
+ @mcp.resource("cortex://status")
223
+ async def get_cli_status() -> str:
224
+ """Get current session status for CLI display.
225
+
226
+ Provides real-time status information that can be used for:
227
+ - Session timer display
228
+ - Activity counts
229
+ - Tool usage statistics
230
+ - Memory creation stats
231
+
232
+ This resource is designed to support future CLI status bar integration.
233
+ """
234
+ try:
235
+ import json
236
+ from datetime import datetime, timezone
237
+ conn = init_database()
238
+ cursor = conn.cursor()
239
+
240
+ status = {
241
+ "timestamp": datetime.now(timezone.utc).isoformat(),
242
+ "session": None,
243
+ "activity_summary": {},
244
+ "memory_summary": {},
245
+ "tool_stats": {},
246
+ }
247
+
248
+ # Get current/most recent session
249
+ cursor.execute("""
250
+ SELECT id, started_at, ended_at, duration_ms
251
+ FROM sessions
252
+ ORDER BY started_at DESC
253
+ LIMIT 1
254
+ """)
255
+ row = cursor.fetchone()
256
+ if row:
257
+ session_id = row["id"]
258
+ started_at = row["started_at"]
259
+ ended_at = row["ended_at"]
260
+ duration_ms = row["duration_ms"]
261
+
262
+ # Calculate session duration
263
+ if started_at:
264
+ try:
265
+ start_time = datetime.fromisoformat(started_at.replace("Z", "+00:00"))
266
+ now = datetime.now(timezone.utc)
267
+ elapsed_seconds = (now - start_time).total_seconds()
268
+ elapsed_minutes = int(elapsed_seconds / 60)
269
+ elapsed_hours = int(elapsed_minutes / 60)
270
+ except Exception:
271
+ elapsed_seconds = 0
272
+ elapsed_minutes = 0
273
+ elapsed_hours = 0
274
+ else:
275
+ elapsed_seconds = elapsed_minutes = elapsed_hours = 0
276
+
277
+ status["session"] = {
278
+ "id": session_id,
279
+ "started_at": started_at,
280
+ "ended_at": ended_at,
281
+ "is_active": ended_at is None,
282
+ "elapsed_seconds": int(elapsed_seconds),
283
+ "elapsed_minutes": elapsed_minutes,
284
+ "elapsed_hours": elapsed_hours,
285
+ "elapsed_display": f"{elapsed_hours}h {elapsed_minutes % 60}m" if elapsed_hours > 0 else f"{elapsed_minutes}m",
286
+ "recorded_duration_ms": duration_ms,
287
+ }
288
+
289
+ # Get activity count for this session
290
+ cursor.execute("""
291
+ SELECT COUNT(*) as count
292
+ FROM activities
293
+ WHERE session_id = ?
294
+ """, (session_id,))
295
+ activity_count = cursor.fetchone()["count"]
296
+
297
+ # Get tool breakdown for this session
298
+ cursor.execute("""
299
+ SELECT tool_name, COUNT(*) as count, SUM(duration_ms) as total_ms
300
+ FROM activities
301
+ WHERE session_id = ? AND tool_name IS NOT NULL
302
+ GROUP BY tool_name
303
+ ORDER BY count DESC
304
+ LIMIT 10
305
+ """, (session_id,))
306
+ tool_stats = {}
307
+ for tool_row in cursor.fetchall():
308
+ tool_stats[tool_row["tool_name"]] = {
309
+ "count": tool_row["count"],
310
+ "total_ms": tool_row["total_ms"],
311
+ }
312
+
313
+ status["activity_summary"] = {
314
+ "session_activity_count": activity_count,
315
+ "top_tools": tool_stats,
316
+ }
317
+
318
+ # Get memories created in this session
319
+ cursor.execute("""
320
+ SELECT COUNT(*) as count
321
+ FROM memories
322
+ WHERE source_session_id = ?
323
+ """, (session_id,))
324
+ memories_created = cursor.fetchone()["count"]
325
+
326
+ status["memory_summary"] = {
327
+ "session_memories_created": memories_created,
328
+ }
329
+
330
+ # Get all-time totals
331
+ cursor.execute("SELECT COUNT(*) FROM activities")
332
+ status["totals"] = {
333
+ "all_activities": cursor.fetchone()[0],
334
+ }
335
+ cursor.execute("SELECT COUNT(*) FROM memories")
336
+ status["totals"]["all_memories"] = cursor.fetchone()[0]
337
+ cursor.execute("SELECT COUNT(*) FROM sessions")
338
+ status["totals"]["all_sessions"] = cursor.fetchone()[0]
339
+
340
+ # Get user message count if table exists
341
+ try:
342
+ cursor.execute("SELECT COUNT(*) FROM user_messages")
343
+ status["totals"]["all_user_messages"] = cursor.fetchone()[0]
344
+ except Exception:
345
+ status["totals"]["all_user_messages"] = 0
346
+
347
+ return json.dumps(status, indent=2)
348
+
349
+ except Exception as e:
350
+ import json
351
+ return json.dumps({"error": str(e)})
352
+
353
+
354
+ def main():
355
+ """Run the MCP server."""
356
+ mcp.run()
357
+
358
+
359
+ if __name__ == "__main__":
360
+ main()
omni_cortex/setup.py ADDED
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env python3
2
+ """Omni Cortex MCP Setup Module.
3
+
4
+ This module automatically configures Omni Cortex MCP for Claude Code:
5
+ 1. Adds the MCP server to ~/.claude.json
6
+ 2. Configures activity logging hooks in Claude's settings.json
7
+
8
+ Usage:
9
+ python -m omni_cortex.setup
10
+ # or after pip install:
11
+ omni-cortex-setup
12
+ """
13
+
14
+ import json
15
+ import os
16
+ import sys
17
+ from pathlib import Path
18
+
19
+
20
+ def get_claude_config_path() -> Path:
21
+ """Get the path to Claude's config file."""
22
+ home = Path.home()
23
+ return home / ".claude.json"
24
+
25
+
26
+ def get_claude_settings_path() -> Path:
27
+ """Get the path to Claude Code's settings.json."""
28
+ home = Path.home()
29
+
30
+ # Check common locations
31
+ if sys.platform == "win32":
32
+ paths = [
33
+ home / ".claude" / "settings.json",
34
+ home / "AppData" / "Roaming" / "Claude" / "settings.json",
35
+ ]
36
+ else:
37
+ paths = [
38
+ home / ".claude" / "settings.json",
39
+ home / ".config" / "claude" / "settings.json",
40
+ ]
41
+
42
+ for path in paths:
43
+ if path.exists():
44
+ return path
45
+
46
+ # Default to .claude/settings.json
47
+ return home / ".claude" / "settings.json"
48
+
49
+
50
+ def get_package_dir() -> Path:
51
+ """Get the directory where omni-cortex is installed."""
52
+ return Path(__file__).parent.parent.parent
53
+
54
+
55
+ def get_hooks_dir() -> Path:
56
+ """Get the hooks directory.
57
+
58
+ Checks multiple locations for hooks:
59
+ 1. Development: <project>/hooks/
60
+ 2. Installed: <site-packages>/../share/omni-cortex/hooks/
61
+ 3. Installed: <prefix>/share/omni-cortex/hooks/
62
+ """
63
+ # Try development location
64
+ package_dir = get_package_dir()
65
+ hooks_dir = package_dir / "hooks"
66
+ if hooks_dir.exists():
67
+ return hooks_dir
68
+
69
+ # Try relative to package
70
+ import omni_cortex
71
+ pkg_path = Path(omni_cortex.__file__).parent
72
+
73
+ # Check various installed locations
74
+ candidates = [
75
+ pkg_path.parent.parent / "hooks",
76
+ pkg_path.parent.parent / "share" / "omni-cortex" / "hooks",
77
+ Path(sys.prefix) / "share" / "omni-cortex" / "hooks",
78
+ Path(sys.base_prefix) / "share" / "omni-cortex" / "hooks",
79
+ ]
80
+
81
+ for candidate in candidates:
82
+ if candidate.exists():
83
+ return candidate
84
+
85
+ # Fall back to package dir even if it doesn't exist
86
+ return package_dir / "hooks"
87
+
88
+
89
+ def setup_mcp_server() -> bool:
90
+ """Add Omni Cortex MCP server to Claude's config."""
91
+ config_path = get_claude_config_path()
92
+
93
+ # Read existing config or create new
94
+ if config_path.exists():
95
+ with open(config_path) as f:
96
+ config = json.load(f)
97
+ else:
98
+ config = {}
99
+
100
+ # Ensure mcpServers exists
101
+ if "mcpServers" not in config:
102
+ config["mcpServers"] = {}
103
+
104
+ # Add omni-cortex server
105
+ config["mcpServers"]["omni-cortex"] = {
106
+ "command": sys.executable,
107
+ "args": ["-m", "omni_cortex.server"],
108
+ }
109
+
110
+ # Write config
111
+ config_path.parent.mkdir(parents=True, exist_ok=True)
112
+ with open(config_path, "w") as f:
113
+ json.dump(config, f, indent=2)
114
+
115
+ print(f" Added MCP server to {config_path}")
116
+ return True
117
+
118
+
119
+ def setup_hooks() -> bool:
120
+ """Configure activity logging hooks in Claude's settings.json."""
121
+ settings_path = get_claude_settings_path()
122
+ hooks_dir = get_hooks_dir()
123
+
124
+ if not hooks_dir.exists():
125
+ print(f" Warning: hooks directory not found at {hooks_dir}")
126
+ print(" Skipping hooks configuration.")
127
+ return False
128
+
129
+ # Read existing settings or create new
130
+ if settings_path.exists():
131
+ with open(settings_path) as f:
132
+ settings = json.load(f)
133
+ else:
134
+ settings = {}
135
+
136
+ # Ensure hooks section exists
137
+ if "hooks" not in settings:
138
+ settings["hooks"] = {}
139
+
140
+ # Get Python executable
141
+ python_exe = sys.executable
142
+
143
+ # Helper to check if hook already exists (handles both old and new format)
144
+ def hook_exists(hook_list, script_name):
145
+ for h in hook_list:
146
+ # Check old format
147
+ cmd = str(h.get("command", ""))
148
+ if "omni-cortex" in cmd or script_name in cmd:
149
+ return True
150
+ # Check new format (matcher + hooks array)
151
+ if "hooks" in h:
152
+ for inner_hook in h.get("hooks", []):
153
+ inner_cmd = str(inner_hook.get("command", ""))
154
+ if "omni-cortex" in inner_cmd or script_name in inner_cmd:
155
+ return True
156
+ return False
157
+
158
+ # Configure hooks (new format with matcher and hooks array)
159
+ hooks_config = {
160
+ "PreToolUse": ("pre_tool_use.py", f'"{python_exe}" "{hooks_dir / "pre_tool_use.py"}"'),
161
+ "PostToolUse": ("post_tool_use.py", f'"{python_exe}" "{hooks_dir / "post_tool_use.py"}"'),
162
+ "Stop": ("stop.py", f'"{python_exe}" "{hooks_dir / "stop.py"}"'),
163
+ }
164
+
165
+ for hook_name, (script_name, command) in hooks_config.items():
166
+ if hook_name not in settings["hooks"]:
167
+ settings["hooks"][hook_name] = []
168
+
169
+ if not hook_exists(settings["hooks"][hook_name], script_name):
170
+ # New format: matcher + hooks array
171
+ settings["hooks"][hook_name].append({
172
+ "matcher": "",
173
+ "hooks": [
174
+ {
175
+ "type": "command",
176
+ "command": command,
177
+ }
178
+ ]
179
+ })
180
+
181
+ # Write settings
182
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
183
+ with open(settings_path, "w") as f:
184
+ json.dump(settings, f, indent=2)
185
+
186
+ print(f" Configured hooks in {settings_path}")
187
+ return True
188
+
189
+
190
+ def uninstall() -> bool:
191
+ """Remove Omni Cortex from Claude's configuration."""
192
+ config_path = get_claude_config_path()
193
+ settings_path = get_claude_settings_path()
194
+
195
+ # Remove from MCP servers
196
+ if config_path.exists():
197
+ with open(config_path) as f:
198
+ config = json.load(f)
199
+
200
+ if "mcpServers" in config and "omni-cortex" in config["mcpServers"]:
201
+ del config["mcpServers"]["omni-cortex"]
202
+ with open(config_path, "w") as f:
203
+ json.dump(config, f, indent=2)
204
+ print(f" Removed MCP server from {config_path}")
205
+
206
+ # Remove hooks
207
+ if settings_path.exists():
208
+ with open(settings_path) as f:
209
+ settings = json.load(f)
210
+
211
+ if "hooks" in settings:
212
+ for hook_name in ["PreToolUse", "PostToolUse", "Stop"]:
213
+ if hook_name in settings["hooks"]:
214
+ settings["hooks"][hook_name] = [
215
+ h for h in settings["hooks"][hook_name]
216
+ if "omni-cortex" not in str(h.get("command", ""))
217
+ and "pre_tool_use" not in str(h.get("command", ""))
218
+ and "post_tool_use" not in str(h.get("command", ""))
219
+ and "stop.py" not in str(h.get("command", ""))
220
+ ]
221
+
222
+ with open(settings_path, "w") as f:
223
+ json.dump(settings, f, indent=2)
224
+ print(f" Removed hooks from {settings_path}")
225
+
226
+ return True
227
+
228
+
229
+ def main():
230
+ """Run the setup process."""
231
+ args = sys.argv[1:]
232
+
233
+ if "--uninstall" in args or "uninstall" in args:
234
+ print("=" * 50)
235
+ print("Omni Cortex MCP Uninstall")
236
+ print("=" * 50)
237
+ print()
238
+ uninstall()
239
+ print()
240
+ print("Uninstall complete. Restart Claude Code.")
241
+ return
242
+
243
+ print("=" * 50)
244
+ print("Omni Cortex MCP Setup")
245
+ print("=" * 50)
246
+ print()
247
+
248
+ # Step 1: Setup MCP server
249
+ print("Step 1: Configuring MCP server...")
250
+ setup_mcp_server()
251
+
252
+ print()
253
+
254
+ # Step 2: Setup hooks
255
+ print("Step 2: Configuring activity hooks...")
256
+ setup_hooks()
257
+
258
+ print()
259
+ print("=" * 50)
260
+ print("Setup complete!")
261
+ print()
262
+ print("Omni Cortex MCP is now ready to use.")
263
+ print("Restart Claude Code for changes to take effect.")
264
+ print()
265
+ print("The MCP will automatically:")
266
+ print(" - Log all tool calls to .omni-cortex/cortex.db")
267
+ print(" - Provide cortex_remember/cortex_recall tools")
268
+ print(" - Track sessions and activities")
269
+ print()
270
+ print("For semantic search, install:")
271
+ print(" pip install sentence-transformers")
272
+ print()
273
+ print("To uninstall: omni-cortex-setup --uninstall")
274
+ print("=" * 50)
275
+
276
+
277
+ if __name__ == "__main__":
278
+ main()
@@ -0,0 +1,13 @@
1
+ """MCP tools for Omni Cortex."""
2
+
3
+ from .memories import register_memory_tools
4
+ from .activities import register_activity_tools
5
+ from .sessions import register_session_tools
6
+ from .utilities import register_utility_tools
7
+
8
+ __all__ = [
9
+ "register_memory_tools",
10
+ "register_activity_tools",
11
+ "register_session_tools",
12
+ "register_utility_tools",
13
+ ]