omni-cortex 1.17.3__tar.gz → 1.17.6__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 (71) hide show
  1. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/PKG-INFO +6 -1
  2. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/main.py +2 -2
  3. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/hooks/post_tool_use.py +2 -0
  4. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/hooks/pre_tool_use.py +2 -0
  5. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/hooks/stop.py +2 -0
  6. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/hooks/subagent_stop.py +2 -0
  7. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/hooks/user_prompt.py +117 -2
  8. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/__init__.py +1 -1
  9. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/database/connection.py +1 -0
  10. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/database/sync.py +257 -2
  11. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/setup.py +9 -1
  12. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/pyproject.toml +10 -3
  13. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/scripts/update_docs_pdfs.py +61 -52
  14. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/.gitignore +0 -0
  15. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/LICENSE +0 -0
  16. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/README.md +0 -0
  17. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/.env.example +0 -0
  18. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/backfill_summaries.py +0 -0
  19. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/chat_service.py +0 -0
  20. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/database.py +0 -0
  21. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/image_service.py +0 -0
  22. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/logging_config.py +0 -0
  23. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/models.py +0 -0
  24. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/project_config.py +0 -0
  25. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/project_scanner.py +0 -0
  26. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/prompt_security.py +0 -0
  27. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/pyproject.toml +0 -0
  28. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/security.py +0 -0
  29. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/uv.lock +0 -0
  30. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/dashboard/backend/websocket_manager.py +0 -0
  31. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/hooks/session_utils.py +0 -0
  32. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/categorization/__init__.py +0 -0
  33. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/categorization/auto_tags.py +0 -0
  34. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/categorization/auto_type.py +0 -0
  35. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/config.py +0 -0
  36. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/dashboard.py +0 -0
  37. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/database/__init__.py +0 -0
  38. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/database/migrations.py +0 -0
  39. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/database/schema.py +0 -0
  40. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/decay/__init__.py +0 -0
  41. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/decay/importance.py +0 -0
  42. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/embeddings/__init__.py +0 -0
  43. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/embeddings/local.py +0 -0
  44. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/models/__init__.py +0 -0
  45. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/models/activity.py +0 -0
  46. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/models/agent.py +0 -0
  47. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/models/memory.py +0 -0
  48. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/models/relationship.py +0 -0
  49. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/models/session.py +0 -0
  50. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/resources/__init__.py +0 -0
  51. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/search/__init__.py +0 -0
  52. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/search/hybrid.py +0 -0
  53. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/search/keyword.py +0 -0
  54. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/search/ranking.py +0 -0
  55. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/search/semantic.py +0 -0
  56. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/server.py +0 -0
  57. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/tools/__init__.py +0 -0
  58. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/tools/activities.py +0 -0
  59. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/tools/memories.py +0 -0
  60. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/tools/sessions.py +0 -0
  61. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/tools/utilities.py +0 -0
  62. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/utils/__init__.py +0 -0
  63. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/utils/formatting.py +0 -0
  64. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/utils/ids.py +0 -0
  65. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/utils/timestamps.py +0 -0
  66. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/omni_cortex/utils/truncation.py +0 -0
  67. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/scripts/check-venv.py +0 -0
  68. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/scripts/generate_storage_architecture_pdf.py +0 -0
  69. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/scripts/import_ken_memories.py +0 -0
  70. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/scripts/populate_session_data.py +0 -0
  71. {omni_cortex-1.17.3 → omni_cortex-1.17.6}/scripts/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omni-cortex
3
- Version: 1.17.3
3
+ Version: 1.17.6
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
@@ -27,6 +27,11 @@ Requires-Dist: mcp>=1.0.0
27
27
  Requires-Dist: pydantic>=2.0.0
28
28
  Requires-Dist: python-dotenv>=1.0.0
29
29
  Requires-Dist: pyyaml>=6.0.0
30
+ Provides-Extra: dashboard
31
+ Requires-Dist: fastapi>=0.115.0; extra == 'dashboard'
32
+ Requires-Dist: uvicorn[standard]>=0.30.0; extra == 'dashboard'
33
+ Requires-Dist: watchdog>=4.0.0; extra == 'dashboard'
34
+ Requires-Dist: websockets>=12.0; extra == 'dashboard'
30
35
  Provides-Extra: dev
31
36
  Requires-Dist: black>=23.0.0; extra == 'dev'
32
37
  Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
@@ -131,10 +131,10 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
131
131
  response.headers["Content-Security-Policy"] = (
132
132
  "default-src 'self'; "
133
133
  "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " # Vue needs these
134
- "style-src 'self' 'unsafe-inline'; " # Tailwind needs inline
134
+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " # Tailwind + Google Fonts
135
135
  "img-src 'self' data: blob: https:; " # Allow AI-generated images
136
136
  "connect-src 'self' ws: wss: https://generativelanguage.googleapis.com; "
137
- "font-src 'self'; "
137
+ "font-src 'self' https://fonts.gstatic.com; " # Google Fonts
138
138
  "frame-ancestors 'none';"
139
139
  )
140
140
 
@@ -148,6 +148,8 @@ def ensure_database(db_path: Path) -> sqlite3.Connection:
148
148
  """
149
149
  db_path.parent.mkdir(parents=True, exist_ok=True)
150
150
  conn = sqlite3.connect(str(db_path))
151
+ conn.execute("PRAGMA journal_mode = WAL")
152
+ conn.execute("PRAGMA busy_timeout = 5000")
151
153
 
152
154
  # Check if schema exists
153
155
  cursor = conn.cursor()
@@ -146,6 +146,8 @@ def ensure_database(db_path: Path) -> sqlite3.Connection:
146
146
  """
147
147
  db_path.parent.mkdir(parents=True, exist_ok=True)
148
148
  conn = sqlite3.connect(str(db_path))
149
+ conn.execute("PRAGMA journal_mode = WAL")
150
+ conn.execute("PRAGMA busy_timeout = 5000")
149
151
 
150
152
  # Check if schema exists
151
153
  cursor = conn.cursor()
@@ -60,6 +60,8 @@ def main():
60
60
 
61
61
  # Connect to database
62
62
  conn = sqlite3.connect(str(db_path))
63
+ conn.execute("PRAGMA journal_mode = WAL")
64
+ conn.execute("PRAGMA busy_timeout = 5000")
63
65
  conn.row_factory = sqlite3.Row
64
66
  cursor = conn.cursor()
65
67
 
@@ -69,6 +69,8 @@ def main():
69
69
 
70
70
  # Connect to database
71
71
  conn = sqlite3.connect(str(db_path))
72
+ conn.execute("PRAGMA journal_mode = WAL")
73
+ conn.execute("PRAGMA busy_timeout = 5000")
72
74
  cursor = conn.cursor()
73
75
 
74
76
  # Log the subagent completion as an activity
@@ -3,6 +3,8 @@
3
3
 
4
4
  This hook is called by Claude Code when the user submits a prompt.
5
5
  It logs the user message to the Cortex database for later style analysis.
6
+ Messages are synced to both the project database and the global database
7
+ to enable cross-project style analysis (Write Like Me skill).
6
8
 
7
9
  Hook configuration for settings.json:
8
10
  {
@@ -39,10 +41,110 @@ def get_db_path() -> Path:
39
41
  return Path(project_path) / ".omni-cortex" / "cortex.db"
40
42
 
41
43
 
44
+ def get_global_db_path() -> Path:
45
+ """Get the global database path."""
46
+ # Check for custom location in environment
47
+ global_dir = os.environ.get("OMNI_CORTEX_GLOBAL_DIR")
48
+ if global_dir:
49
+ return Path(global_dir) / "cortex.db"
50
+
51
+ # Default to D:\Projects\.omni-cortex for this system
52
+ # Fallback to ~/.omni-cortex if that doesn't exist
53
+ windows_global = Path("D:/Projects/.omni-cortex")
54
+ if windows_global.exists():
55
+ return windows_global / "cortex.db"
56
+
57
+ return Path.home() / ".omni-cortex" / "cortex.db"
58
+
59
+
60
+ def ensure_global_user_messages_table(conn: sqlite3.Connection) -> None:
61
+ """Ensure user_messages table exists in the global database."""
62
+ cursor = conn.cursor()
63
+ cursor.execute(
64
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='user_messages'"
65
+ )
66
+ if cursor.fetchone() is None:
67
+ conn.executescript("""
68
+ CREATE TABLE IF NOT EXISTS user_messages (
69
+ id TEXT PRIMARY KEY,
70
+ session_id TEXT,
71
+ timestamp TEXT NOT NULL,
72
+ content TEXT NOT NULL,
73
+ word_count INTEGER,
74
+ char_count INTEGER,
75
+ line_count INTEGER,
76
+ has_code_blocks INTEGER DEFAULT 0,
77
+ has_questions INTEGER DEFAULT 0,
78
+ has_commands INTEGER DEFAULT 0,
79
+ tone_indicators TEXT,
80
+ project_path TEXT,
81
+ metadata TEXT
82
+ );
83
+ CREATE INDEX IF NOT EXISTS idx_global_user_messages_timestamp ON user_messages(timestamp DESC);
84
+ CREATE INDEX IF NOT EXISTS idx_global_user_messages_project ON user_messages(project_path);
85
+ """)
86
+ conn.commit()
87
+
88
+
89
+ def sync_to_global(
90
+ message_id: str,
91
+ session_id: str,
92
+ timestamp: str,
93
+ content: str,
94
+ analysis: dict,
95
+ project_path: str,
96
+ ) -> bool:
97
+ """Sync user message to the global database for cross-project style analysis."""
98
+ try:
99
+ global_db_path = get_global_db_path()
100
+ global_db_path.parent.mkdir(parents=True, exist_ok=True)
101
+
102
+ conn = sqlite3.connect(str(global_db_path))
103
+ conn.execute("PRAGMA journal_mode = WAL")
104
+ conn.execute("PRAGMA busy_timeout = 5000")
105
+ ensure_global_user_messages_table(conn)
106
+
107
+ cursor = conn.cursor()
108
+ cursor.execute(
109
+ """
110
+ INSERT INTO user_messages (
111
+ id, session_id, timestamp, content, word_count, char_count,
112
+ line_count, has_code_blocks, has_questions, has_commands,
113
+ tone_indicators, project_path
114
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
115
+ ON CONFLICT(id) DO UPDATE SET
116
+ content = excluded.content,
117
+ tone_indicators = excluded.tone_indicators
118
+ """,
119
+ (
120
+ message_id,
121
+ session_id,
122
+ timestamp,
123
+ content,
124
+ analysis["word_count"],
125
+ analysis["char_count"],
126
+ analysis["line_count"],
127
+ analysis["has_code_blocks"],
128
+ analysis["has_questions"],
129
+ analysis["has_commands"],
130
+ analysis["tone_indicators"],
131
+ project_path,
132
+ ),
133
+ )
134
+ conn.commit()
135
+ conn.close()
136
+ return True
137
+ except Exception:
138
+ # Silently fail - global sync is best-effort
139
+ return False
140
+
141
+
42
142
  def ensure_database(db_path: Path) -> sqlite3.Connection:
43
143
  """Ensure database exists and has user_messages table."""
44
144
  db_path.parent.mkdir(parents=True, exist_ok=True)
45
145
  conn = sqlite3.connect(str(db_path))
146
+ conn.execute("PRAGMA journal_mode = WAL")
147
+ conn.execute("PRAGMA busy_timeout = 5000")
46
148
 
47
149
  # Check if user_messages table exists
48
150
  cursor = conn.cursor()
@@ -177,7 +279,10 @@ def main():
177
279
  # Generate message ID
178
280
  message_id = generate_id()
179
281
 
180
- # Insert message record
282
+ # Generate timestamp
283
+ timestamp = datetime.now(timezone.utc).isoformat()
284
+
285
+ # Insert message record into project database
181
286
  cursor = conn.cursor()
182
287
  cursor.execute(
183
288
  """
@@ -190,7 +295,7 @@ def main():
190
295
  (
191
296
  message_id,
192
297
  session_id,
193
- datetime.now(timezone.utc).isoformat(),
298
+ timestamp,
194
299
  prompt,
195
300
  analysis["word_count"],
196
301
  analysis["char_count"],
@@ -205,6 +310,16 @@ def main():
205
310
  conn.commit()
206
311
  conn.close()
207
312
 
313
+ # Sync to global database for cross-project style analysis
314
+ sync_to_global(
315
+ message_id=message_id,
316
+ session_id=session_id,
317
+ timestamp=timestamp,
318
+ content=prompt,
319
+ analysis=analysis,
320
+ project_path=project_path,
321
+ )
322
+
208
323
  # Return empty response (don't modify prompt)
209
324
  print(json.dumps({}))
210
325
 
@@ -1,3 +1,3 @@
1
1
  """Omni Cortex MCP - Universal Memory System for Claude Code."""
2
2
 
3
- __version__ = "1.17.1"
3
+ __version__ = "1.17.6"
@@ -23,6 +23,7 @@ def _configure_connection(conn: sqlite3.Connection) -> None:
23
23
  conn.row_factory = sqlite3.Row
24
24
  conn.execute("PRAGMA foreign_keys = ON")
25
25
  conn.execute("PRAGMA journal_mode = WAL")
26
+ conn.execute("PRAGMA busy_timeout = 5000")
26
27
  conn.execute("PRAGMA synchronous = NORMAL")
27
28
  conn.execute("PRAGMA cache_size = -64000") # 64MB cache
28
29
 
@@ -1,7 +1,8 @@
1
1
  """Global index synchronization for cross-project memory search.
2
2
 
3
- This module handles syncing memories from project-local databases to the
4
- global database at ~/.omni-cortex/global.db, enabling cross-project search.
3
+ This module handles syncing memories and user messages from project-local
4
+ databases to the global database at ~/.omni-cortex/global.db, enabling
5
+ cross-project search and style analysis.
5
6
  """
6
7
 
7
8
  import json
@@ -17,6 +18,260 @@ from ..utils.timestamps import now_iso
17
18
  logger = logging.getLogger(__name__)
18
19
 
19
20
 
21
+ # SQL to create user_messages table in global database
22
+ USER_MESSAGES_SCHEMA = """
23
+ CREATE TABLE IF NOT EXISTS user_messages (
24
+ id TEXT PRIMARY KEY,
25
+ session_id TEXT,
26
+ timestamp TEXT NOT NULL,
27
+ content TEXT NOT NULL,
28
+ word_count INTEGER,
29
+ char_count INTEGER,
30
+ line_count INTEGER,
31
+ has_code_blocks INTEGER DEFAULT 0,
32
+ has_questions INTEGER DEFAULT 0,
33
+ has_commands INTEGER DEFAULT 0,
34
+ tone_indicators TEXT,
35
+ project_path TEXT,
36
+ metadata TEXT
37
+ );
38
+
39
+ CREATE INDEX IF NOT EXISTS idx_global_user_messages_timestamp ON user_messages(timestamp DESC);
40
+ CREATE INDEX IF NOT EXISTS idx_global_user_messages_project ON user_messages(project_path);
41
+ """
42
+
43
+
44
+ def ensure_user_messages_table(conn: sqlite3.Connection) -> None:
45
+ """Ensure user_messages table exists in the database."""
46
+ cursor = conn.cursor()
47
+ cursor.execute(
48
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='user_messages'"
49
+ )
50
+ if cursor.fetchone() is None:
51
+ # Table doesn't exist, create it
52
+ for statement in USER_MESSAGES_SCHEMA.strip().split(';'):
53
+ statement = statement.strip()
54
+ if statement:
55
+ try:
56
+ conn.execute(statement)
57
+ except sqlite3.OperationalError as e:
58
+ if "already exists" not in str(e).lower():
59
+ raise
60
+ conn.commit()
61
+ logger.info("Created user_messages table in global database")
62
+
63
+
64
+ def sync_user_message_to_global(
65
+ message_id: str,
66
+ session_id: Optional[str],
67
+ timestamp: str,
68
+ content: str,
69
+ word_count: int,
70
+ char_count: int,
71
+ line_count: int,
72
+ has_code_blocks: int,
73
+ has_questions: int,
74
+ has_commands: int,
75
+ tone_indicators: str,
76
+ project_path: str,
77
+ ) -> bool:
78
+ """Sync a user message to the global database.
79
+
80
+ This enables cross-project style analysis for the Write Like Me skill.
81
+
82
+ Args:
83
+ message_id: Unique message ID
84
+ session_id: Associated session ID
85
+ timestamp: ISO 8601 timestamp
86
+ content: Message content
87
+ word_count: Word count
88
+ char_count: Character count
89
+ line_count: Line count
90
+ has_code_blocks: Whether message has code blocks (0/1)
91
+ has_questions: Whether message has questions (0/1)
92
+ has_commands: Whether message starts with slash command (0/1)
93
+ tone_indicators: JSON array of tone indicators
94
+ project_path: Source project path
95
+
96
+ Returns:
97
+ True if synced successfully
98
+ """
99
+ config = load_config()
100
+ if not config.global_sync_enabled:
101
+ return False
102
+
103
+ try:
104
+ global_conn = init_database(is_global=True)
105
+
106
+ # Ensure user_messages table exists
107
+ ensure_user_messages_table(global_conn)
108
+
109
+ cursor = global_conn.cursor()
110
+
111
+ # Upsert the message
112
+ cursor.execute(
113
+ """
114
+ INSERT INTO user_messages (
115
+ id, session_id, timestamp, content, word_count, char_count,
116
+ line_count, has_code_blocks, has_questions, has_commands,
117
+ tone_indicators, project_path
118
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
119
+ ON CONFLICT(id) DO UPDATE SET
120
+ content = excluded.content,
121
+ tone_indicators = excluded.tone_indicators
122
+ """,
123
+ (
124
+ message_id,
125
+ session_id,
126
+ timestamp,
127
+ content,
128
+ word_count,
129
+ char_count,
130
+ line_count,
131
+ has_code_blocks,
132
+ has_questions,
133
+ has_commands,
134
+ tone_indicators,
135
+ project_path,
136
+ ),
137
+ )
138
+
139
+ global_conn.commit()
140
+ logger.debug(f"Synced user message {message_id} to global index")
141
+ return True
142
+
143
+ except Exception as e:
144
+ logger.warning(f"Failed to sync user message {message_id} to global: {e}")
145
+ return False
146
+
147
+
148
+ def get_global_user_messages(
149
+ limit: int = 100,
150
+ project_filter: Optional[str] = None,
151
+ exclude_commands: bool = True,
152
+ exclude_short: bool = True,
153
+ min_length: int = 30,
154
+ ) -> list[dict]:
155
+ """Get user messages from the global database for style analysis.
156
+
157
+ Args:
158
+ limit: Maximum messages to return
159
+ project_filter: Filter by project path (substring match)
160
+ exclude_commands: Exclude messages starting with /
161
+ exclude_short: Exclude very short messages
162
+ min_length: Minimum content length when exclude_short=True
163
+
164
+ Returns:
165
+ List of message dicts suitable for style analysis
166
+ """
167
+ try:
168
+ global_conn = init_database(is_global=True)
169
+
170
+ # Ensure table exists
171
+ ensure_user_messages_table(global_conn)
172
+
173
+ cursor = global_conn.cursor()
174
+
175
+ where_conditions = ["1=1"]
176
+ params: list = []
177
+
178
+ if exclude_commands:
179
+ where_conditions.append("has_commands = 0")
180
+
181
+ if exclude_short:
182
+ where_conditions.append("char_count >= ?")
183
+ params.append(min_length)
184
+
185
+ if project_filter:
186
+ where_conditions.append("project_path LIKE ?")
187
+ params.append(f"%{project_filter}%")
188
+
189
+ params.append(limit)
190
+
191
+ cursor.execute(
192
+ f"""
193
+ SELECT * FROM user_messages
194
+ WHERE {' AND '.join(where_conditions)}
195
+ ORDER BY timestamp DESC
196
+ LIMIT ?
197
+ """,
198
+ params,
199
+ )
200
+
201
+ results = []
202
+ for row in cursor.fetchall():
203
+ results.append({
204
+ "id": row["id"],
205
+ "content": row["content"],
206
+ "word_count": row["word_count"],
207
+ "char_count": row["char_count"],
208
+ "has_questions": bool(row["has_questions"]),
209
+ "has_code_blocks": bool(row["has_code_blocks"]),
210
+ "tone_indicators": row["tone_indicators"],
211
+ "project_path": row["project_path"],
212
+ "timestamp": row["timestamp"],
213
+ })
214
+
215
+ return results
216
+
217
+ except Exception as e:
218
+ logger.error(f"Failed to get global user messages: {e}")
219
+ return []
220
+
221
+
222
+ def sync_all_project_user_messages() -> int:
223
+ """Sync all user messages from current project to global index.
224
+
225
+ Returns:
226
+ Number of messages synced
227
+ """
228
+ config = load_config()
229
+ if not config.global_sync_enabled:
230
+ return 0
231
+
232
+ try:
233
+ project_conn = init_database()
234
+ project_path = str(get_project_path())
235
+
236
+ cursor = project_conn.cursor()
237
+
238
+ # Check if user_messages table exists in project
239
+ cursor.execute(
240
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='user_messages'"
241
+ )
242
+ if cursor.fetchone() is None:
243
+ logger.debug("No user_messages table in project database")
244
+ return 0
245
+
246
+ cursor.execute("SELECT * FROM user_messages")
247
+
248
+ count = 0
249
+ for row in cursor.fetchall():
250
+ synced = sync_user_message_to_global(
251
+ message_id=row["id"],
252
+ session_id=row["session_id"],
253
+ timestamp=row["timestamp"],
254
+ content=row["content"],
255
+ word_count=row["word_count"] or 0,
256
+ char_count=row["char_count"] or 0,
257
+ line_count=row["line_count"] or 0,
258
+ has_code_blocks=row["has_code_blocks"] or 0,
259
+ has_questions=row["has_questions"] or 0,
260
+ has_commands=row["has_commands"] or 0,
261
+ tone_indicators=row["tone_indicators"] or "[]",
262
+ project_path=project_path,
263
+ )
264
+ if synced:
265
+ count += 1
266
+
267
+ logger.info(f"Synced {count} user messages to global index")
268
+ return count
269
+
270
+ except Exception as e:
271
+ logger.error(f"Failed to sync project user messages: {e}")
272
+ return 0
273
+
274
+
20
275
  def sync_memory_to_global(
21
276
  memory_id: str,
22
277
  content: str,
@@ -108,11 +108,19 @@ def setup_mcp_server() -> bool:
108
108
  config["mcpServers"] = {}
109
109
 
110
110
  # Add omni-cortex server
111
- config["mcpServers"]["omni-cortex"] = {
111
+ server_config = {
112
112
  "command": sys.executable,
113
113
  "args": ["-m", "omni_cortex.server"],
114
114
  }
115
115
 
116
+ # For editable installs, add PYTHONPATH so the MCP server can find the source
117
+ src_dir = Path(__file__).parent.parent
118
+ repo_root = src_dir.parent
119
+ if (repo_root / "pyproject.toml").exists() and (repo_root / ".git").exists():
120
+ server_config["env"] = {"PYTHONPATH": str(src_dir)}
121
+
122
+ config["mcpServers"]["omni-cortex"] = server_config
123
+
116
124
  # Write config
117
125
  config_path.parent.mkdir(parents=True, exist_ok=True)
118
126
  with open(config_path, "w") as f:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "omni-cortex"
7
- version = "1.17.3"
7
+ version = "1.17.6"
8
8
  description = "Give Claude Code a perfect memory - auto-logs everything, searches smartly, and gets smarter over time"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -46,6 +46,12 @@ semantic = [
46
46
  "sentence-transformers>=2.2.0",
47
47
  "numpy>=1.24.0",
48
48
  ]
49
+ dashboard = [
50
+ "fastapi>=0.115.0",
51
+ "uvicorn[standard]>=0.30.0",
52
+ "websockets>=12.0",
53
+ "watchdog>=4.0.0",
54
+ ]
49
55
  dev = [
50
56
  "pytest>=7.0.0",
51
57
  "pytest-asyncio>=0.21.0",
@@ -60,6 +66,7 @@ omni-cortex-dashboard = "omni_cortex.dashboard:main"
60
66
 
61
67
  [tool.hatch.build]
62
68
  sources = ["src"]
69
+ dev-mode-dirs = ["src"]
63
70
 
64
71
  [tool.hatch.build.targets.wheel]
65
72
  packages = ["omni_cortex"]
@@ -69,8 +76,7 @@ packages = ["omni_cortex"]
69
76
  [tool.hatch.build.targets.wheel.force-include]
70
77
  "hooks" = "omni_cortex/_bundled/hooks"
71
78
  "dashboard/backend" = "omni_cortex/_bundled/dashboard/backend"
72
- # Note: frontend/dist is not force-included because it requires npm build
73
- # The backend serves API endpoints; frontend can be built separately if needed
79
+ "dashboard/frontend/dist" = "omni_cortex/_bundled/dashboard/frontend/dist"
74
80
 
75
81
  [tool.hatch.build.targets.sdist]
76
82
  include = [
@@ -78,6 +84,7 @@ include = [
78
84
  "/hooks",
79
85
  "/scripts",
80
86
  "/dashboard/backend",
87
+ "/dashboard/frontend/dist",
81
88
  "/README.md",
82
89
  ]
83
90
 
@@ -181,7 +181,7 @@ def create_callout_box(text, styles, color=PRIMARY):
181
181
  def create_dashboard_guide():
182
182
  """Create the updated Dashboard Guide PDF"""
183
183
  styles = get_styles()
184
- filename = os.path.join(OUTPUT_DIR, 'OmniCortex_DashboardGuide.pdf')
184
+ filename = os.path.join(OUTPUT_DIR, '04_OmniCortex_DashboardGuide.pdf')
185
185
 
186
186
  doc = SimpleDocTemplate(
187
187
  filename,
@@ -527,7 +527,7 @@ def create_dashboard_guide():
527
527
  def create_feature_comparison():
528
528
  """Create the updated Feature Comparison PDF"""
529
529
  styles = get_styles()
530
- filename = os.path.join(OUTPUT_DIR, 'OmniCortex_FeatureComparison.pdf')
530
+ filename = os.path.join(OUTPUT_DIR, '05_OmniCortex_FeatureComparison.pdf')
531
531
 
532
532
  doc = SimpleDocTemplate(
533
533
  filename,
@@ -690,7 +690,7 @@ def create_feature_comparison():
690
690
  def create_storage_architecture():
691
691
  """Create the updated Storage Architecture PDF"""
692
692
  styles = get_styles()
693
- filename = os.path.join(OUTPUT_DIR, 'OmniCortex_StorageArchitecture.pdf')
693
+ filename = os.path.join(OUTPUT_DIR, '06_OmniCortex_StorageArchitecture.pdf')
694
694
 
695
695
  doc = SimpleDocTemplate(
696
696
  filename,
@@ -921,7 +921,7 @@ def create_storage_architecture():
921
921
  def create_quick_start():
922
922
  """Create the updated Quick Start PDF"""
923
923
  styles = get_styles()
924
- filename = os.path.join(OUTPUT_DIR, 'OmniCortex_QuickStart.pdf')
924
+ filename = os.path.join(OUTPUT_DIR, '02_OmniCortex_QuickStart.pdf')
925
925
 
926
926
  doc = SimpleDocTemplate(
927
927
  filename,
@@ -942,50 +942,69 @@ def create_quick_start():
942
942
  styles['OmniBody']
943
943
  ))
944
944
 
945
- # 1. Installation
946
- elements.append(Paragraph("1. Installation", styles['SectionHeader']))
945
+ # Installation - 3 Simple Commands
946
+ elements.append(Paragraph("3 Commands to Get Started", styles['SectionHeader']))
947
+ elements.append(Paragraph(
948
+ "OmniCortex uses an automatic setup process - no manual configuration required!",
949
+ styles['OmniBody']
950
+ ))
951
+ elements.append(Spacer(1, 8))
952
+
953
+ # Step 1
954
+ elements.append(Paragraph("<b>Step 1: Install the Package</b>", styles['OmniBody']))
947
955
  elements.append(create_callout_box("pip install omni-cortex", styles, ACCENT))
948
956
  elements.append(Spacer(1, 8))
949
- elements.append(Paragraph("Or with optional features:", styles['OmniBody']))
950
- elements.append(create_callout_box(
951
- "pip install omni-cortex[semantic] # Semantic search<br/>"
952
- "pip install omni-cortex[all] # All features",
953
- styles, SECONDARY
957
+
958
+ # Step 2
959
+ elements.append(Paragraph("<b>Step 2: Run Automatic Setup</b>", styles['OmniBody']))
960
+ elements.append(create_callout_box("omni-cortex-setup", styles, ACCENT))
961
+ elements.append(Paragraph(
962
+ "This automatically configures the MCP server and hooks in Claude Code. "
963
+ "No manual JSON editing required!",
964
+ styles['OmniBody']
954
965
  ))
966
+ elements.append(Spacer(1, 8))
955
967
 
956
- # 2. Configure
957
- elements.append(Paragraph("2. Configure Claude Code", styles['SectionHeader']))
968
+ # Step 3
969
+ elements.append(Paragraph("<b>Step 3: Launch Dashboard (Optional)</b>", styles['OmniBody']))
970
+ elements.append(create_callout_box("omni-cortex-dashboard", styles, ACCENT))
958
971
  elements.append(Paragraph(
959
- "Add OmniCortex to your Claude Code MCP configuration:",
972
+ "Opens the web dashboard at http://localhost:8765",
960
973
  styles['OmniBody']
961
974
  ))
962
- elements.append(Paragraph("~/.claude/claude_desktop_config.json:", styles['OmniBody']))
963
-
964
- config_text = '''{
965
- "mcpServers": {
966
- "omni-cortex": {
967
- "command": "omni-cortex",
968
- "args": ["serve"]
969
- }
970
- }
971
- }'''
972
- elements.append(Paragraph(f"<pre>{config_text}</pre>", styles['CodeBlock']))
973
-
974
- # 3. First Use
975
- elements.append(Paragraph("3. First Use", styles['SectionHeader']))
975
+ elements.append(Spacer(1, 12))
976
+
977
+ # Restart reminder
978
+ elements.append(create_callout_box(
979
+ "Important: Restart Claude Code after running omni-cortex-setup",
980
+ styles, colors.HexColor('#E74C3C')
981
+ ))
982
+ elements.append(Spacer(1, 12))
983
+
984
+ # Optional features
985
+ elements.append(Paragraph("Optional: Enhanced Features", styles['SubHeader']))
986
+ elements.append(create_callout_box(
987
+ "pip install omni-cortex[semantic] # AI-powered semantic search<br/>"
988
+ "pip install omni-cortex[dashboard] # Dashboard dependencies",
989
+ styles, SECONDARY
990
+ ))
991
+
992
+ # What happens automatically
993
+ elements.append(Paragraph("What Happens Automatically", styles['SectionHeader']))
976
994
  elements.append(Paragraph(
977
- "Start Claude Code in any project directory. OmniCortex will automatically:",
995
+ "After setup, when you start Claude Code in any project directory:",
978
996
  styles['OmniBody']
979
997
  ))
980
998
  for item in [
981
- "Create .omni-cortex/ directory in your project",
982
- "Initialize cortex.db SQLite database",
983
- "Make memory tools available to Claude",
999
+ "Creates .omni-cortex/ directory in your project",
1000
+ "Initializes cortex.db SQLite database",
1001
+ "Makes 18 memory tools available to Claude",
1002
+ "Starts logging all tool activity automatically",
984
1003
  ]:
985
1004
  elements.append(Paragraph(f"• {item}", styles['BulletItem']))
986
1005
 
987
- # 4. Core Tools
988
- elements.append(Paragraph("4. Core Tools", styles['SectionHeader']))
1006
+ # Core Tools
1007
+ elements.append(Paragraph("Core Tools", styles['SectionHeader']))
989
1008
  tools_data = [
990
1009
  ['Tool', 'Purpose', 'Example'],
991
1010
  ['cortex_remember', 'Store information', 'Store this API pattern'],
@@ -993,24 +1012,14 @@ def create_quick_start():
993
1012
  ['cortex_list_memories', 'Browse all', 'Show recent decisions'],
994
1013
  ['cortex_update_memory', 'Modify memory', 'Add tags to memory'],
995
1014
  ['cortex_link_memories', 'Create relations', 'Link related solutions'],
1015
+ ['cortex_start_session', 'Begin work session', 'Get previous context'],
996
1016
  ]
997
1017
  elements.append(create_table(tools_data, [1.5*inch, 1.3*inch, 2.2*inch]))
998
1018
 
999
- # 5. Web Dashboard
1000
- elements.append(Paragraph("5. Web Dashboard", styles['SectionHeader']))
1001
- elements.append(create_callout_box("omni-cortex-dashboard", styles, ACCENT))
1002
- elements.append(Spacer(1, 8))
1003
- elements.append(Paragraph(
1004
- "Opens a visual interface at http://localhost:8765 for browsing memories, viewing activity logs, "
1005
- "and analyzing your knowledge base. Includes <b>Response Composer</b> for generating style-matched "
1006
- "responses to community messages.",
1007
- styles['OmniBody']
1008
- ))
1009
-
1010
1019
  elements.append(PageBreak())
1011
1020
 
1012
- # 6. Storage Locations
1013
- elements.append(Paragraph("6. Storage Locations", styles['SectionHeader']))
1021
+ # Storage Locations
1022
+ elements.append(Paragraph("Storage Locations", styles['SectionHeader']))
1014
1023
  storage_data = [
1015
1024
  ['Location', 'Path', 'Purpose'],
1016
1025
  ['Project DB', '.omni-cortex/cortex.db', 'Project memories & activities'],
@@ -1019,8 +1028,8 @@ def create_quick_start():
1019
1028
  ]
1020
1029
  elements.append(create_table(storage_data, [1.2*inch, 2.2*inch, 2.1*inch]))
1021
1030
 
1022
- # 7. Memory Types
1023
- elements.append(Paragraph("7. Memory Types", styles['SectionHeader']))
1031
+ # Memory Types
1032
+ elements.append(Paragraph("Memory Types", styles['SectionHeader']))
1024
1033
  types_data = [
1025
1034
  ['Type', 'Use For'],
1026
1035
  ['fact', 'Technical facts, API details, configurations'],
@@ -1032,8 +1041,8 @@ def create_quick_start():
1032
1041
  ]
1033
1042
  elements.append(create_table(types_data, [1.5*inch, 4*inch]))
1034
1043
 
1035
- # 8. Pro Tips
1036
- elements.append(Paragraph("8. Pro Tips", styles['SectionHeader']))
1044
+ # Pro Tips
1045
+ elements.append(Paragraph("Pro Tips", styles['SectionHeader']))
1037
1046
  for item in [
1038
1047
  'Ask Claude to "remember this" when you solve a tricky problem',
1039
1048
  "Use tags liberally - they're searchable and filterable",
@@ -1076,7 +1085,7 @@ def create_quick_start():
1076
1085
  def create_troubleshooting_faq():
1077
1086
  """Create the updated Troubleshooting FAQ PDF"""
1078
1087
  styles = get_styles()
1079
- filename = os.path.join(OUTPUT_DIR, 'OmniCortex_TroubleshootingFAQ.pdf')
1088
+ filename = os.path.join(OUTPUT_DIR, '07_OmniCortex_TroubleshootingFAQ.pdf')
1080
1089
 
1081
1090
  doc = SimpleDocTemplate(
1082
1091
  filename,
File without changes
File without changes
File without changes