omni-cortex 1.17.2__py3-none-any.whl → 1.17.4__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 (93) hide show
  1. omni_cortex/_bundled/dashboard/backend/.env.example +12 -0
  2. omni_cortex/_bundled/dashboard/backend/backfill_summaries.py +280 -0
  3. omni_cortex/_bundled/dashboard/backend/chat_service.py +631 -0
  4. omni_cortex/_bundled/dashboard/backend/database.py +1773 -0
  5. omni_cortex/_bundled/dashboard/backend/image_service.py +552 -0
  6. omni_cortex/_bundled/dashboard/backend/logging_config.py +122 -0
  7. omni_cortex/_bundled/dashboard/backend/main.py +1888 -0
  8. omni_cortex/_bundled/dashboard/backend/models.py +472 -0
  9. omni_cortex/_bundled/dashboard/backend/project_config.py +170 -0
  10. omni_cortex/_bundled/dashboard/backend/project_scanner.py +164 -0
  11. omni_cortex/_bundled/dashboard/backend/prompt_security.py +111 -0
  12. omni_cortex/_bundled/dashboard/backend/pyproject.toml +23 -0
  13. omni_cortex/_bundled/dashboard/backend/security.py +104 -0
  14. omni_cortex/_bundled/dashboard/backend/test_database.py +301 -0
  15. omni_cortex/_bundled/dashboard/backend/tmpclaude-2dfa-cwd +1 -0
  16. omni_cortex/_bundled/dashboard/backend/tmpclaude-c460-cwd +1 -0
  17. omni_cortex/_bundled/dashboard/backend/uv.lock +1110 -0
  18. omni_cortex/_bundled/dashboard/backend/websocket_manager.py +104 -0
  19. omni_cortex/_bundled/dashboard/frontend/dist/assets/index-CQlQK3nE.js +551 -0
  20. omni_cortex/_bundled/dashboard/frontend/dist/assets/index-CmUNNfe4.css +1 -0
  21. omni_cortex/_bundled/dashboard/frontend/dist/index.html +14 -0
  22. omni_cortex/_bundled/hooks/post_tool_use.py +497 -0
  23. omni_cortex/_bundled/hooks/pre_tool_use.py +277 -0
  24. omni_cortex/_bundled/hooks/session_utils.py +186 -0
  25. omni_cortex/_bundled/hooks/stop.py +219 -0
  26. omni_cortex/_bundled/hooks/subagent_stop.py +120 -0
  27. omni_cortex/_bundled/hooks/user_prompt.py +331 -0
  28. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/main.py +2 -2
  29. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/hooks/user_prompt.py +113 -2
  30. {omni_cortex-1.17.2.dist-info → omni_cortex-1.17.4.dist-info}/METADATA +6 -1
  31. omni_cortex-1.17.4.dist-info/RECORD +53 -0
  32. omni_cortex/__init__.py +0 -3
  33. omni_cortex/categorization/__init__.py +0 -9
  34. omni_cortex/categorization/auto_tags.py +0 -166
  35. omni_cortex/categorization/auto_type.py +0 -165
  36. omni_cortex/config.py +0 -141
  37. omni_cortex/dashboard.py +0 -232
  38. omni_cortex/database/__init__.py +0 -24
  39. omni_cortex/database/connection.py +0 -137
  40. omni_cortex/database/migrations.py +0 -210
  41. omni_cortex/database/schema.py +0 -212
  42. omni_cortex/database/sync.py +0 -421
  43. omni_cortex/decay/__init__.py +0 -7
  44. omni_cortex/decay/importance.py +0 -147
  45. omni_cortex/embeddings/__init__.py +0 -35
  46. omni_cortex/embeddings/local.py +0 -442
  47. omni_cortex/models/__init__.py +0 -20
  48. omni_cortex/models/activity.py +0 -265
  49. omni_cortex/models/agent.py +0 -144
  50. omni_cortex/models/memory.py +0 -395
  51. omni_cortex/models/relationship.py +0 -206
  52. omni_cortex/models/session.py +0 -290
  53. omni_cortex/resources/__init__.py +0 -1
  54. omni_cortex/search/__init__.py +0 -22
  55. omni_cortex/search/hybrid.py +0 -197
  56. omni_cortex/search/keyword.py +0 -204
  57. omni_cortex/search/ranking.py +0 -127
  58. omni_cortex/search/semantic.py +0 -232
  59. omni_cortex/server.py +0 -360
  60. omni_cortex/setup.py +0 -278
  61. omni_cortex/tools/__init__.py +0 -13
  62. omni_cortex/tools/activities.py +0 -453
  63. omni_cortex/tools/memories.py +0 -536
  64. omni_cortex/tools/sessions.py +0 -311
  65. omni_cortex/tools/utilities.py +0 -477
  66. omni_cortex/utils/__init__.py +0 -13
  67. omni_cortex/utils/formatting.py +0 -282
  68. omni_cortex/utils/ids.py +0 -72
  69. omni_cortex/utils/timestamps.py +0 -129
  70. omni_cortex/utils/truncation.py +0 -111
  71. omni_cortex-1.17.2.dist-info/RECORD +0 -65
  72. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/.env.example +0 -0
  73. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/backfill_summaries.py +0 -0
  74. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/chat_service.py +0 -0
  75. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/database.py +0 -0
  76. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/image_service.py +0 -0
  77. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/logging_config.py +0 -0
  78. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/models.py +0 -0
  79. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/project_config.py +0 -0
  80. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/project_scanner.py +0 -0
  81. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/prompt_security.py +0 -0
  82. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/pyproject.toml +0 -0
  83. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/security.py +0 -0
  84. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/uv.lock +0 -0
  85. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/dashboard/backend/websocket_manager.py +0 -0
  86. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/hooks/post_tool_use.py +0 -0
  87. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/hooks/pre_tool_use.py +0 -0
  88. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/hooks/session_utils.py +0 -0
  89. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/hooks/stop.py +0 -0
  90. {omni_cortex-1.17.2.data → omni_cortex-1.17.4.data}/data/share/omni-cortex/hooks/subagent_stop.py +0 -0
  91. {omni_cortex-1.17.2.dist-info → omni_cortex-1.17.4.dist-info}/WHEEL +0 -0
  92. {omni_cortex-1.17.2.dist-info → omni_cortex-1.17.4.dist-info}/entry_points.txt +0 -0
  93. {omni_cortex-1.17.2.dist-info → omni_cortex-1.17.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+ """SubagentStop hook - logs when a subagent completes.
3
+
4
+ This hook is called when a subagent (spawned by the Task tool) finishes.
5
+ It logs the subagent completion and any results.
6
+
7
+ Hook configuration for settings.json:
8
+ {
9
+ "hooks": {
10
+ "SubagentStop": [
11
+ {
12
+ "type": "command",
13
+ "command": "python hooks/subagent_stop.py"
14
+ }
15
+ ]
16
+ }
17
+ }
18
+ """
19
+
20
+ import json
21
+ import sys
22
+ import os
23
+ import sqlite3
24
+ from datetime import datetime, timezone
25
+ from pathlib import Path
26
+
27
+
28
+ def get_db_path() -> Path:
29
+ """Get the database path for the current project."""
30
+ project_path = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
31
+ return Path(project_path) / ".omni-cortex" / "cortex.db"
32
+
33
+
34
+ def generate_id() -> str:
35
+ """Generate a unique activity ID."""
36
+ timestamp_ms = int(datetime.now().timestamp() * 1000)
37
+ random_hex = os.urandom(4).hex()
38
+ return f"act_{timestamp_ms}_{random_hex}"
39
+
40
+
41
+ def truncate(text: str, max_length: int = 10000) -> str:
42
+ """Truncate text to max length."""
43
+ if len(text) <= max_length:
44
+ return text
45
+ return text[:max_length - 20] + "\n... [truncated]"
46
+
47
+
48
+ def main():
49
+ """Process SubagentStop hook."""
50
+ try:
51
+ # Read input from stdin
52
+ input_data = json.load(sys.stdin)
53
+
54
+ db_path = get_db_path()
55
+
56
+ # Only log if database exists
57
+ if not db_path.exists():
58
+ print(json.dumps({}))
59
+ return
60
+
61
+ # Extract data from hook input
62
+ subagent_id = input_data.get("subagent_id")
63
+ subagent_type = input_data.get("subagent_type", "subagent")
64
+ result = input_data.get("result", {})
65
+
66
+ session_id = os.environ.get("CLAUDE_SESSION_ID")
67
+ project_path = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
68
+ now = datetime.now(timezone.utc).isoformat()
69
+
70
+ # Connect to database
71
+ conn = sqlite3.connect(str(db_path))
72
+ cursor = conn.cursor()
73
+
74
+ # Log the subagent completion as an activity
75
+ cursor.execute(
76
+ """
77
+ INSERT INTO activities (
78
+ id, session_id, agent_id, timestamp, event_type,
79
+ tool_name, tool_output, success, project_path
80
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
81
+ """,
82
+ (
83
+ generate_id(),
84
+ session_id,
85
+ subagent_id,
86
+ now,
87
+ "subagent_stop",
88
+ f"subagent_{subagent_type}",
89
+ truncate(json.dumps(result, default=str)),
90
+ 1,
91
+ project_path,
92
+ ),
93
+ )
94
+
95
+ # Update or create agent record
96
+ cursor.execute(
97
+ """
98
+ INSERT INTO agents (id, name, type, first_seen, last_seen, total_activities)
99
+ VALUES (?, ?, ?, ?, ?, 1)
100
+ ON CONFLICT(id) DO UPDATE SET
101
+ last_seen = ?,
102
+ total_activities = total_activities + 1
103
+ """,
104
+ (subagent_id, None, "subagent", now, now, now),
105
+ )
106
+
107
+ conn.commit()
108
+ conn.close()
109
+
110
+ print(json.dumps({}))
111
+
112
+ except Exception as e:
113
+ # Hooks should never block
114
+ print(json.dumps({"systemMessage": f"Cortex subagent_stop: {e}"}))
115
+
116
+ sys.exit(0)
117
+
118
+
119
+ if __name__ == "__main__":
120
+ main()
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env python3
2
+ """UserPromptSubmit hook - captures user messages for style analysis.
3
+
4
+ This hook is called by Claude Code when the user submits a prompt.
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).
8
+
9
+ Hook configuration for settings.json:
10
+ {
11
+ "hooks": {
12
+ "UserPromptSubmit": [
13
+ {
14
+ "hooks": [
15
+ {
16
+ "type": "command",
17
+ "command": "python hooks/user_prompt.py"
18
+ }
19
+ ]
20
+ }
21
+ ]
22
+ }
23
+ }
24
+ """
25
+
26
+ import json
27
+ import re
28
+ import sys
29
+ import os
30
+ import sqlite3
31
+ from datetime import datetime, timezone
32
+ from pathlib import Path
33
+
34
+ # Import shared session management
35
+ from session_utils import get_or_create_session
36
+
37
+
38
+ def get_db_path() -> Path:
39
+ """Get the database path for the current project."""
40
+ project_path = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
41
+ return Path(project_path) / ".omni-cortex" / "cortex.db"
42
+
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
+ ensure_global_user_messages_table(conn)
104
+
105
+ cursor = conn.cursor()
106
+ cursor.execute(
107
+ """
108
+ INSERT INTO user_messages (
109
+ id, session_id, timestamp, content, word_count, char_count,
110
+ line_count, has_code_blocks, has_questions, has_commands,
111
+ tone_indicators, project_path
112
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
113
+ ON CONFLICT(id) DO UPDATE SET
114
+ content = excluded.content,
115
+ tone_indicators = excluded.tone_indicators
116
+ """,
117
+ (
118
+ message_id,
119
+ session_id,
120
+ timestamp,
121
+ content,
122
+ analysis["word_count"],
123
+ analysis["char_count"],
124
+ analysis["line_count"],
125
+ analysis["has_code_blocks"],
126
+ analysis["has_questions"],
127
+ analysis["has_commands"],
128
+ analysis["tone_indicators"],
129
+ project_path,
130
+ ),
131
+ )
132
+ conn.commit()
133
+ conn.close()
134
+ return True
135
+ except Exception:
136
+ # Silently fail - global sync is best-effort
137
+ return False
138
+
139
+
140
+ def ensure_database(db_path: Path) -> sqlite3.Connection:
141
+ """Ensure database exists and has user_messages table."""
142
+ db_path.parent.mkdir(parents=True, exist_ok=True)
143
+ conn = sqlite3.connect(str(db_path))
144
+
145
+ # Check if user_messages table exists
146
+ cursor = conn.cursor()
147
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user_messages'")
148
+ if cursor.fetchone() is None:
149
+ # Apply minimal schema for user_messages
150
+ conn.executescript("""
151
+ CREATE TABLE IF NOT EXISTS user_messages (
152
+ id TEXT PRIMARY KEY,
153
+ session_id TEXT,
154
+ timestamp TEXT NOT NULL,
155
+ content TEXT NOT NULL,
156
+ word_count INTEGER,
157
+ char_count INTEGER,
158
+ line_count INTEGER,
159
+ has_code_blocks INTEGER DEFAULT 0,
160
+ has_questions INTEGER DEFAULT 0,
161
+ has_commands INTEGER DEFAULT 0,
162
+ tone_indicators TEXT,
163
+ project_path TEXT,
164
+ metadata TEXT
165
+ );
166
+ CREATE INDEX IF NOT EXISTS idx_user_messages_timestamp ON user_messages(timestamp DESC);
167
+ """)
168
+ conn.commit()
169
+
170
+ return conn
171
+
172
+
173
+ def generate_id() -> str:
174
+ """Generate a unique message ID."""
175
+ timestamp_ms = int(datetime.now().timestamp() * 1000)
176
+ random_hex = os.urandom(4).hex()
177
+ return f"msg_{timestamp_ms}_{random_hex}"
178
+
179
+
180
+ def analyze_message(content: str) -> dict:
181
+ """Analyze message characteristics for style profiling."""
182
+ # Basic counts
183
+ word_count = len(content.split())
184
+ char_count = len(content)
185
+ line_count = len(content.splitlines()) or 1
186
+
187
+ # Detect code blocks
188
+ has_code_blocks = 1 if re.search(r'```[\s\S]*?```|`[^`]+`', content) else 0
189
+
190
+ # Detect questions
191
+ has_questions = 1 if re.search(r'\?|^(what|how|why|when|where|who|which|can|could|would|should|is|are|do|does|did)\b', content, re.IGNORECASE | re.MULTILINE) else 0
192
+
193
+ # Detect slash commands
194
+ has_commands = 1 if content.strip().startswith('/') else 0
195
+
196
+ # Tone indicators
197
+ tone_indicators = []
198
+
199
+ # Urgency markers
200
+ if re.search(r'\b(urgent|asap|immediately|quick|fast|hurry)\b', content, re.IGNORECASE):
201
+ tone_indicators.append("urgent")
202
+
203
+ # Polite markers
204
+ if re.search(r'\b(please|thanks|thank you|appreciate|kindly)\b', content, re.IGNORECASE):
205
+ tone_indicators.append("polite")
206
+
207
+ # Direct/imperative
208
+ if re.match(r'^(fix|add|remove|update|change|create|delete|run|test|check|show|list|find)\b', content.strip(), re.IGNORECASE):
209
+ tone_indicators.append("direct")
210
+
211
+ # Questioning/exploratory
212
+ if has_questions:
213
+ tone_indicators.append("inquisitive")
214
+
215
+ # Technical
216
+ if re.search(r'\b(function|class|method|variable|api|database|server|error|bug|issue)\b', content, re.IGNORECASE):
217
+ tone_indicators.append("technical")
218
+
219
+ # Casual
220
+ if re.search(r'\b(hey|hi|yo|cool|awesome|great|nice)\b', content, re.IGNORECASE):
221
+ tone_indicators.append("casual")
222
+
223
+ return {
224
+ "word_count": word_count,
225
+ "char_count": char_count,
226
+ "line_count": line_count,
227
+ "has_code_blocks": has_code_blocks,
228
+ "has_questions": has_questions,
229
+ "has_commands": has_commands,
230
+ "tone_indicators": json.dumps(tone_indicators),
231
+ }
232
+
233
+
234
+ def main():
235
+ """Process UserPromptSubmit hook."""
236
+ try:
237
+ # Read input from stdin
238
+ import select
239
+ if sys.platform != "win32":
240
+ ready, _, _ = select.select([sys.stdin], [], [], 5.0)
241
+ if not ready:
242
+ print(json.dumps({}))
243
+ return
244
+
245
+ raw_input = sys.stdin.read()
246
+ if not raw_input or not raw_input.strip():
247
+ print(json.dumps({}))
248
+ return
249
+
250
+ input_data = json.loads(raw_input)
251
+
252
+ # Extract user prompt
253
+ prompt = input_data.get("prompt", "")
254
+ if not prompt or not prompt.strip():
255
+ print(json.dumps({}))
256
+ return
257
+
258
+ # Skip very short messages (likely just commands)
259
+ if len(prompt.strip()) < 3:
260
+ print(json.dumps({}))
261
+ return
262
+
263
+ project_path = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
264
+
265
+ # Initialize database
266
+ db_path = get_db_path()
267
+ conn = ensure_database(db_path)
268
+
269
+ # Get or create session
270
+ session_id = get_or_create_session(conn, project_path)
271
+
272
+ # Analyze message
273
+ analysis = analyze_message(prompt)
274
+
275
+ # Generate message ID
276
+ message_id = generate_id()
277
+
278
+ # Generate timestamp
279
+ timestamp = datetime.now(timezone.utc).isoformat()
280
+
281
+ # Insert message record into project database
282
+ cursor = conn.cursor()
283
+ cursor.execute(
284
+ """
285
+ INSERT INTO user_messages (
286
+ id, session_id, timestamp, content, word_count, char_count,
287
+ line_count, has_code_blocks, has_questions, has_commands,
288
+ tone_indicators, project_path
289
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
290
+ """,
291
+ (
292
+ message_id,
293
+ session_id,
294
+ timestamp,
295
+ prompt,
296
+ analysis["word_count"],
297
+ analysis["char_count"],
298
+ analysis["line_count"],
299
+ analysis["has_code_blocks"],
300
+ analysis["has_questions"],
301
+ analysis["has_commands"],
302
+ analysis["tone_indicators"],
303
+ project_path,
304
+ ),
305
+ )
306
+ conn.commit()
307
+ conn.close()
308
+
309
+ # Sync to global database for cross-project style analysis
310
+ sync_to_global(
311
+ message_id=message_id,
312
+ session_id=session_id,
313
+ timestamp=timestamp,
314
+ content=prompt,
315
+ analysis=analysis,
316
+ project_path=project_path,
317
+ )
318
+
319
+ # Return empty response (don't modify prompt)
320
+ print(json.dumps({}))
321
+
322
+ except Exception as e:
323
+ # Hooks should never block - log error but continue
324
+ # Don't print system message to avoid polluting user experience
325
+ print(json.dumps({}))
326
+
327
+ sys.exit(0)
328
+
329
+
330
+ if __name__ == "__main__":
331
+ main()
@@ -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
 
@@ -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,6 +41,102 @@ 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
+ ensure_global_user_messages_table(conn)
104
+
105
+ cursor = conn.cursor()
106
+ cursor.execute(
107
+ """
108
+ INSERT INTO user_messages (
109
+ id, session_id, timestamp, content, word_count, char_count,
110
+ line_count, has_code_blocks, has_questions, has_commands,
111
+ tone_indicators, project_path
112
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
113
+ ON CONFLICT(id) DO UPDATE SET
114
+ content = excluded.content,
115
+ tone_indicators = excluded.tone_indicators
116
+ """,
117
+ (
118
+ message_id,
119
+ session_id,
120
+ timestamp,
121
+ content,
122
+ analysis["word_count"],
123
+ analysis["char_count"],
124
+ analysis["line_count"],
125
+ analysis["has_code_blocks"],
126
+ analysis["has_questions"],
127
+ analysis["has_commands"],
128
+ analysis["tone_indicators"],
129
+ project_path,
130
+ ),
131
+ )
132
+ conn.commit()
133
+ conn.close()
134
+ return True
135
+ except Exception:
136
+ # Silently fail - global sync is best-effort
137
+ return False
138
+
139
+
42
140
  def ensure_database(db_path: Path) -> sqlite3.Connection:
43
141
  """Ensure database exists and has user_messages table."""
44
142
  db_path.parent.mkdir(parents=True, exist_ok=True)
@@ -177,7 +275,10 @@ def main():
177
275
  # Generate message ID
178
276
  message_id = generate_id()
179
277
 
180
- # Insert message record
278
+ # Generate timestamp
279
+ timestamp = datetime.now(timezone.utc).isoformat()
280
+
281
+ # Insert message record into project database
181
282
  cursor = conn.cursor()
182
283
  cursor.execute(
183
284
  """
@@ -190,7 +291,7 @@ def main():
190
291
  (
191
292
  message_id,
192
293
  session_id,
193
- datetime.now(timezone.utc).isoformat(),
294
+ timestamp,
194
295
  prompt,
195
296
  analysis["word_count"],
196
297
  analysis["char_count"],
@@ -205,6 +306,16 @@ def main():
205
306
  conn.commit()
206
307
  conn.close()
207
308
 
309
+ # Sync to global database for cross-project style analysis
310
+ sync_to_global(
311
+ message_id=message_id,
312
+ session_id=session_id,
313
+ timestamp=timestamp,
314
+ content=prompt,
315
+ analysis=analysis,
316
+ project_path=project_path,
317
+ )
318
+
208
319
  # Return empty response (don't modify prompt)
209
320
  print(json.dumps({}))
210
321
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omni-cortex
3
- Version: 1.17.2
3
+ Version: 1.17.4
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'
@@ -0,0 +1,53 @@
1
+ omni_cortex/_bundled/hooks/post_tool_use.py,sha256=zdaKChi8zOghRlHswisCBSQE3kW1MtmM6AFfI_ivvpI,16581
2
+ omni_cortex/_bundled/hooks/pre_tool_use.py,sha256=3_V6Qw5m40eGrMmm5i94vINzeVxmcJvivdPa69H3AOI,8585
3
+ omni_cortex/_bundled/hooks/session_utils.py,sha256=3SKPCytqWuRPOupWdzmwBoKBDJqtLcT1Nle_pueDQUY,5746
4
+ omni_cortex/_bundled/hooks/stop.py,sha256=UroliJsyIS9_lj29-1d_r-80V4AfTMUFCaOjJZv3lwM,6976
5
+ omni_cortex/_bundled/hooks/subagent_stop.py,sha256=V9HQSFGNOfkg8ZCstPEy4h5V8BP4AbrVr8teFzN1kNk,3314
6
+ omni_cortex/_bundled/hooks/user_prompt.py,sha256=4cY-at78ZaaOaTUjUMl6mDXNUwtoLg8geypi6EfQzQM,10717
7
+ omni_cortex/_bundled/dashboard/backend/.env.example,sha256=9xS7-UiWlMddRwzlyyyKNHAMlNTsgH-2sPV266guJpQ,372
8
+ omni_cortex/_bundled/dashboard/backend/backfill_summaries.py,sha256=ElchfcBv4pmVr2PsePCgFlCyuvf4_jDJj_C3AmMhu7U,8973
9
+ omni_cortex/_bundled/dashboard/backend/chat_service.py,sha256=iEDpAw8OVaHM2312VPcAM-w6dYabUpjaTvWl1jGhqi0,20948
10
+ omni_cortex/_bundled/dashboard/backend/database.py,sha256=byISCVgVziqFhJ8j8FC99ueTAoo-E0BJ6bk8KUvW3Mg,57610
11
+ omni_cortex/_bundled/dashboard/backend/image_service.py,sha256=NP6ojFpHb6iNTYRkXqYu1CL6WvooZpZ54mjLiWSWG_g,19205
12
+ omni_cortex/_bundled/dashboard/backend/logging_config.py,sha256=WnunFGET9zlsn9WBpVsio2zI7BiUQanE0xzAQQxIhII,3944
13
+ omni_cortex/_bundled/dashboard/backend/main.py,sha256=vfNQQOL-KEVg1NV-XTlweA3lQqRaQxaT2-lQx5Z4ZEs,67238
14
+ omni_cortex/_bundled/dashboard/backend/models.py,sha256=mP_szTXuVE4ZWHGubLeCt1-ltroO8OO0vrGC3IM6AJ0,13122
15
+ omni_cortex/_bundled/dashboard/backend/project_config.py,sha256=ZxGoeRpHvN5qQyf2hRxrAZiHrPSwdQp59f0di6O1LKM,4352
16
+ omni_cortex/_bundled/dashboard/backend/project_scanner.py,sha256=lwFXS8iJbOoxf7FAyo2TjH25neaMHiJ8B3jS57XxtDI,5713
17
+ omni_cortex/_bundled/dashboard/backend/prompt_security.py,sha256=LcdZhYy1CfpSq_4BPO6lMJ15phc2ZXLUSBAnAvODVCI,3423
18
+ omni_cortex/_bundled/dashboard/backend/pyproject.toml,sha256=9pbbGQXLe1Xd06nZAtDySCHIlfMWvPaB-C6tGZR6umc,502
19
+ omni_cortex/_bundled/dashboard/backend/security.py,sha256=nQsoPE0n5dtY9ive00d33W1gL48GgK7C5Ae0BK2oW2k,3479
20
+ omni_cortex/_bundled/dashboard/backend/test_database.py,sha256=Zcu5rgz1iprnzEANo5MIPgK7mwF97Prerp7QU4MjPfo,9504
21
+ omni_cortex/_bundled/dashboard/backend/tmpclaude-2dfa-cwd,sha256=XAwk7miagzW8cn6LsjnPgONAMSxkRMWNapzJaOodR9Y,42
22
+ omni_cortex/_bundled/dashboard/backend/tmpclaude-c460-cwd,sha256=XAwk7miagzW8cn6LsjnPgONAMSxkRMWNapzJaOodR9Y,42
23
+ omni_cortex/_bundled/dashboard/backend/uv.lock,sha256=miB9zGGSirBkjDE-OZTPCnv43Yc98xuAz_Ne8vTNFHg,186004
24
+ omni_cortex/_bundled/dashboard/backend/websocket_manager.py,sha256=gNQLd94AcC-InumGQmUolREhiogCzilYWpLN8SRZjHI,3645
25
+ omni_cortex/_bundled/dashboard/frontend/dist/index.html,sha256=eN9-5y-KTvQ5BHs-Bh7fJO0WW5uh3unBHPLxzlKhc58,503
26
+ omni_cortex/_bundled/dashboard/frontend/dist/assets/index-CQlQK3nE.js,sha256=3vBwHNt62Xf9mO4REkQ1cXcQirRjLYEi8V6EbYZKi48,724613
27
+ omni_cortex/_bundled/dashboard/frontend/dist/assets/index-CmUNNfe4.css,sha256=_8e4zs4P6JHjtpr7K7jE90_X0c2TgvSOALngsQNlXDA,77476
28
+ omni_cortex-1.17.4.data/data/share/omni-cortex/hooks/post_tool_use.py,sha256=zdaKChi8zOghRlHswisCBSQE3kW1MtmM6AFfI_ivvpI,16581
29
+ omni_cortex-1.17.4.data/data/share/omni-cortex/hooks/pre_tool_use.py,sha256=3_V6Qw5m40eGrMmm5i94vINzeVxmcJvivdPa69H3AOI,8585
30
+ omni_cortex-1.17.4.data/data/share/omni-cortex/hooks/session_utils.py,sha256=3SKPCytqWuRPOupWdzmwBoKBDJqtLcT1Nle_pueDQUY,5746
31
+ omni_cortex-1.17.4.data/data/share/omni-cortex/hooks/stop.py,sha256=UroliJsyIS9_lj29-1d_r-80V4AfTMUFCaOjJZv3lwM,6976
32
+ omni_cortex-1.17.4.data/data/share/omni-cortex/hooks/subagent_stop.py,sha256=V9HQSFGNOfkg8ZCstPEy4h5V8BP4AbrVr8teFzN1kNk,3314
33
+ omni_cortex-1.17.4.data/data/share/omni-cortex/hooks/user_prompt.py,sha256=4cY-at78ZaaOaTUjUMl6mDXNUwtoLg8geypi6EfQzQM,10717
34
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/.env.example,sha256=9xS7-UiWlMddRwzlyyyKNHAMlNTsgH-2sPV266guJpQ,372
35
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/backfill_summaries.py,sha256=ElchfcBv4pmVr2PsePCgFlCyuvf4_jDJj_C3AmMhu7U,8973
36
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/chat_service.py,sha256=iEDpAw8OVaHM2312VPcAM-w6dYabUpjaTvWl1jGhqi0,20948
37
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/database.py,sha256=byISCVgVziqFhJ8j8FC99ueTAoo-E0BJ6bk8KUvW3Mg,57610
38
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/image_service.py,sha256=NP6ojFpHb6iNTYRkXqYu1CL6WvooZpZ54mjLiWSWG_g,19205
39
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/logging_config.py,sha256=WnunFGET9zlsn9WBpVsio2zI7BiUQanE0xzAQQxIhII,3944
40
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/main.py,sha256=vfNQQOL-KEVg1NV-XTlweA3lQqRaQxaT2-lQx5Z4ZEs,67238
41
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/models.py,sha256=mP_szTXuVE4ZWHGubLeCt1-ltroO8OO0vrGC3IM6AJ0,13122
42
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/project_config.py,sha256=ZxGoeRpHvN5qQyf2hRxrAZiHrPSwdQp59f0di6O1LKM,4352
43
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/project_scanner.py,sha256=lwFXS8iJbOoxf7FAyo2TjH25neaMHiJ8B3jS57XxtDI,5713
44
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/prompt_security.py,sha256=LcdZhYy1CfpSq_4BPO6lMJ15phc2ZXLUSBAnAvODVCI,3423
45
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/pyproject.toml,sha256=9pbbGQXLe1Xd06nZAtDySCHIlfMWvPaB-C6tGZR6umc,502
46
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/security.py,sha256=nQsoPE0n5dtY9ive00d33W1gL48GgK7C5Ae0BK2oW2k,3479
47
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/uv.lock,sha256=miB9zGGSirBkjDE-OZTPCnv43Yc98xuAz_Ne8vTNFHg,186004
48
+ omni_cortex-1.17.4.data/data/share/omni-cortex/dashboard/backend/websocket_manager.py,sha256=gNQLd94AcC-InumGQmUolREhiogCzilYWpLN8SRZjHI,3645
49
+ omni_cortex-1.17.4.dist-info/METADATA,sha256=1Gxyp0Jjq1cWb4Hdb_mCnOhfea7Mn2i4MVxd4UBwf4s,15962
50
+ omni_cortex-1.17.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
51
+ omni_cortex-1.17.4.dist-info/entry_points.txt,sha256=rohx4mFH2ffZmMb9QXPZmFf-ZGjA3jpKVDVeET-ttiM,150
52
+ omni_cortex-1.17.4.dist-info/licenses/LICENSE,sha256=oG_397owMmi-Umxp5sYocJ6RPohp9_bDNnnEu9OUphg,1072
53
+ omni_cortex-1.17.4.dist-info/RECORD,,