omni-cortex 1.6.0__py3-none-any.whl → 1.7.0__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.
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/hooks/post_tool_use.py +173 -129
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/hooks/pre_tool_use.py +2 -126
- omni_cortex-1.7.0.data/data/share/omni-cortex/hooks/session_utils.py +186 -0
- {omni_cortex-1.6.0.dist-info → omni_cortex-1.7.0.dist-info}/METADATA +36 -7
- omni_cortex-1.7.0.dist-info/RECORD +25 -0
- omni_cortex-1.6.0.dist-info/RECORD +0 -24
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/.env.example +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/backfill_summaries.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/chat_service.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/database.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/image_service.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/logging_config.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/main.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/models.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/project_config.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/project_scanner.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/prompt_security.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/pyproject.toml +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/security.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/uv.lock +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/websocket_manager.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/hooks/stop.py +0 -0
- {omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/hooks/subagent_stop.py +0 -0
- {omni_cortex-1.6.0.dist-info → omni_cortex-1.7.0.dist-info}/WHEEL +0 -0
- {omni_cortex-1.6.0.dist-info → omni_cortex-1.7.0.dist-info}/entry_points.txt +0 -0
- {omni_cortex-1.6.0.dist-info → omni_cortex-1.7.0.dist-info}/licenses/LICENSE +0 -0
{omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/hooks/post_tool_use.py
RENAMED
|
@@ -22,14 +22,12 @@ import re
|
|
|
22
22
|
import sys
|
|
23
23
|
import os
|
|
24
24
|
import sqlite3
|
|
25
|
-
import time
|
|
26
25
|
from datetime import datetime, timezone
|
|
27
26
|
from pathlib import Path
|
|
28
|
-
from typing import Optional
|
|
29
27
|
|
|
28
|
+
# Import shared session management
|
|
29
|
+
from session_utils import get_or_create_session
|
|
30
30
|
|
|
31
|
-
# Session timeout in seconds (4 hours of inactivity = new session)
|
|
32
|
-
SESSION_TIMEOUT_SECONDS = 4 * 60 * 60
|
|
33
31
|
|
|
34
32
|
# Patterns for sensitive field names that should be redacted
|
|
35
33
|
SENSITIVE_FIELD_PATTERNS = [
|
|
@@ -41,128 +39,6 @@ SENSITIVE_FIELD_PATTERNS = [
|
|
|
41
39
|
]
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
def generate_session_id() -> str:
|
|
45
|
-
"""Generate a unique session ID matching the MCP format."""
|
|
46
|
-
timestamp_ms = int(time.time() * 1000)
|
|
47
|
-
random_hex = os.urandom(4).hex()
|
|
48
|
-
return f"sess_{timestamp_ms}_{random_hex}"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def get_session_file_path() -> Path:
|
|
52
|
-
"""Get the path to the current session file."""
|
|
53
|
-
project_path = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
|
|
54
|
-
return Path(project_path) / ".omni-cortex" / "current_session.json"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def load_session_file() -> Optional[dict]:
|
|
58
|
-
"""Load the current session from file if it exists and is valid."""
|
|
59
|
-
session_file = get_session_file_path()
|
|
60
|
-
if not session_file.exists():
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
try:
|
|
64
|
-
with open(session_file, "r") as f:
|
|
65
|
-
return json.load(f)
|
|
66
|
-
except (json.JSONDecodeError, IOError):
|
|
67
|
-
return None
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def save_session_file(session_data: dict) -> None:
|
|
71
|
-
"""Save the current session to file."""
|
|
72
|
-
session_file = get_session_file_path()
|
|
73
|
-
session_file.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
-
|
|
75
|
-
with open(session_file, "w") as f:
|
|
76
|
-
json.dump(session_data, f, indent=2)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def is_session_valid(session_data: dict) -> bool:
|
|
80
|
-
"""Check if a session is still valid (not timed out)."""
|
|
81
|
-
last_activity = session_data.get("last_activity_at")
|
|
82
|
-
if not last_activity:
|
|
83
|
-
return False
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
last_time = datetime.fromisoformat(last_activity.replace("Z", "+00:00"))
|
|
87
|
-
now = datetime.now(timezone.utc)
|
|
88
|
-
elapsed_seconds = (now - last_time).total_seconds()
|
|
89
|
-
return elapsed_seconds < SESSION_TIMEOUT_SECONDS
|
|
90
|
-
except (ValueError, TypeError):
|
|
91
|
-
return False
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def create_session_in_db(conn: sqlite3.Connection, session_id: str, project_path: str) -> None:
|
|
95
|
-
"""Create a new session record in the database."""
|
|
96
|
-
cursor = conn.cursor()
|
|
97
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
98
|
-
|
|
99
|
-
# Check if sessions table exists (it might not if only activities table was created)
|
|
100
|
-
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'")
|
|
101
|
-
if cursor.fetchone() is None:
|
|
102
|
-
# Create sessions table with minimal schema
|
|
103
|
-
conn.executescript("""
|
|
104
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
105
|
-
id TEXT PRIMARY KEY,
|
|
106
|
-
project_path TEXT NOT NULL,
|
|
107
|
-
started_at TEXT NOT NULL,
|
|
108
|
-
ended_at TEXT,
|
|
109
|
-
summary TEXT,
|
|
110
|
-
tags TEXT,
|
|
111
|
-
metadata TEXT
|
|
112
|
-
);
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at DESC);
|
|
114
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_path);
|
|
115
|
-
""")
|
|
116
|
-
conn.commit()
|
|
117
|
-
|
|
118
|
-
cursor.execute(
|
|
119
|
-
"""
|
|
120
|
-
INSERT OR IGNORE INTO sessions (id, project_path, started_at)
|
|
121
|
-
VALUES (?, ?, ?)
|
|
122
|
-
""",
|
|
123
|
-
(session_id, project_path, now),
|
|
124
|
-
)
|
|
125
|
-
conn.commit()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def get_or_create_session(conn: sqlite3.Connection, project_path: str) -> str:
|
|
129
|
-
"""Get the current session ID, creating a new one if needed.
|
|
130
|
-
|
|
131
|
-
Session management logic:
|
|
132
|
-
1. Check for existing session file
|
|
133
|
-
2. If exists and not timed out, use it and update last_activity
|
|
134
|
-
3. If doesn't exist or timed out, create new session
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
The session ID to use for activity logging
|
|
138
|
-
"""
|
|
139
|
-
session_data = load_session_file()
|
|
140
|
-
now_iso = datetime.now(timezone.utc).isoformat()
|
|
141
|
-
|
|
142
|
-
if session_data and is_session_valid(session_data):
|
|
143
|
-
# Update last activity time
|
|
144
|
-
session_data["last_activity_at"] = now_iso
|
|
145
|
-
save_session_file(session_data)
|
|
146
|
-
return session_data["session_id"]
|
|
147
|
-
|
|
148
|
-
# Create new session
|
|
149
|
-
session_id = generate_session_id()
|
|
150
|
-
|
|
151
|
-
# Create in database
|
|
152
|
-
create_session_in_db(conn, session_id, project_path)
|
|
153
|
-
|
|
154
|
-
# Save to file
|
|
155
|
-
session_data = {
|
|
156
|
-
"session_id": session_id,
|
|
157
|
-
"project_path": project_path,
|
|
158
|
-
"started_at": now_iso,
|
|
159
|
-
"last_activity_at": now_iso,
|
|
160
|
-
}
|
|
161
|
-
save_session_file(session_data)
|
|
162
|
-
|
|
163
|
-
return session_id
|
|
164
|
-
|
|
165
|
-
|
|
166
42
|
def redact_sensitive_fields(data: dict) -> dict:
|
|
167
43
|
"""Redact sensitive fields from a dictionary for safe logging.
|
|
168
44
|
|
|
@@ -253,6 +129,144 @@ def truncate(text: str, max_length: int = 10000) -> str:
|
|
|
253
129
|
return text[:max_length - 20] + "\n... [truncated]"
|
|
254
130
|
|
|
255
131
|
|
|
132
|
+
def extract_skill_info(tool_input: dict, project_path: str) -> tuple:
|
|
133
|
+
"""Extract skill name and scope from Skill tool input.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Tuple of (skill_name, command_scope)
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
skill_name = tool_input.get("skill", "")
|
|
140
|
+
if not skill_name:
|
|
141
|
+
return None, None
|
|
142
|
+
|
|
143
|
+
# Determine scope by checking file locations
|
|
144
|
+
project_cmd = Path(project_path) / ".claude" / "commands" / f"{skill_name}.md"
|
|
145
|
+
if project_cmd.exists():
|
|
146
|
+
return skill_name, "project"
|
|
147
|
+
|
|
148
|
+
universal_cmd = Path.home() / ".claude" / "commands" / f"{skill_name}.md"
|
|
149
|
+
if universal_cmd.exists():
|
|
150
|
+
return skill_name, "universal"
|
|
151
|
+
|
|
152
|
+
return skill_name, "unknown"
|
|
153
|
+
except Exception:
|
|
154
|
+
return None, None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def extract_mcp_server(tool_name: str) -> str:
|
|
158
|
+
"""Extract MCP server name from tool name pattern mcp__servername__toolname."""
|
|
159
|
+
if not tool_name or not tool_name.startswith("mcp__"):
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
parts = tool_name.split("__")
|
|
163
|
+
if len(parts) >= 3:
|
|
164
|
+
return parts[1]
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def ensure_analytics_columns(conn: sqlite3.Connection) -> None:
|
|
169
|
+
"""Ensure command analytics columns exist in activities table."""
|
|
170
|
+
cursor = conn.cursor()
|
|
171
|
+
columns = cursor.execute("PRAGMA table_info(activities)").fetchall()
|
|
172
|
+
column_names = [col[1] for col in columns]
|
|
173
|
+
|
|
174
|
+
new_columns = [
|
|
175
|
+
("command_name", "TEXT"),
|
|
176
|
+
("command_scope", "TEXT"),
|
|
177
|
+
("mcp_server", "TEXT"),
|
|
178
|
+
("skill_name", "TEXT"),
|
|
179
|
+
("summary", "TEXT"),
|
|
180
|
+
("summary_detail", "TEXT"),
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
for col_name, col_type in new_columns:
|
|
184
|
+
if col_name not in column_names:
|
|
185
|
+
cursor.execute(f"ALTER TABLE activities ADD COLUMN {col_name} {col_type}")
|
|
186
|
+
|
|
187
|
+
conn.commit()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def generate_summary(tool_name: str, tool_input: dict, success: bool) -> tuple:
|
|
191
|
+
"""Generate short and detailed summaries for an activity.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Tuple of (summary, summary_detail)
|
|
195
|
+
"""
|
|
196
|
+
if not tool_name:
|
|
197
|
+
return None, None
|
|
198
|
+
|
|
199
|
+
input_data = tool_input if isinstance(tool_input, dict) else {}
|
|
200
|
+
short = ""
|
|
201
|
+
detail = ""
|
|
202
|
+
|
|
203
|
+
if tool_name == "Read":
|
|
204
|
+
path = input_data.get("file_path", "unknown")
|
|
205
|
+
filename = Path(path).name if path else "file"
|
|
206
|
+
short = f"Read file: {filename}"
|
|
207
|
+
detail = f"Reading contents of {path}"
|
|
208
|
+
|
|
209
|
+
elif tool_name == "Write":
|
|
210
|
+
path = input_data.get("file_path", "unknown")
|
|
211
|
+
filename = Path(path).name if path else "file"
|
|
212
|
+
short = f"Write file: {filename}"
|
|
213
|
+
detail = f"Writing/creating file at {path}"
|
|
214
|
+
|
|
215
|
+
elif tool_name == "Edit":
|
|
216
|
+
path = input_data.get("file_path", "unknown")
|
|
217
|
+
filename = Path(path).name if path else "file"
|
|
218
|
+
short = f"Edit file: {filename}"
|
|
219
|
+
detail = f"Editing {path}"
|
|
220
|
+
|
|
221
|
+
elif tool_name == "Bash":
|
|
222
|
+
cmd = str(input_data.get("command", ""))[:50]
|
|
223
|
+
short = f"Run: {cmd}..."
|
|
224
|
+
detail = f"Executing: {input_data.get('command', 'unknown')}"
|
|
225
|
+
|
|
226
|
+
elif tool_name == "Grep":
|
|
227
|
+
pattern = input_data.get("pattern", "")
|
|
228
|
+
short = f"Search: {pattern[:30]}"
|
|
229
|
+
detail = f"Searching for pattern: {pattern}"
|
|
230
|
+
|
|
231
|
+
elif tool_name == "Glob":
|
|
232
|
+
pattern = input_data.get("pattern", "")
|
|
233
|
+
short = f"Find files: {pattern[:30]}"
|
|
234
|
+
detail = f"Finding files matching: {pattern}"
|
|
235
|
+
|
|
236
|
+
elif tool_name == "Skill":
|
|
237
|
+
skill = input_data.get("skill", "unknown")
|
|
238
|
+
short = f"Run skill: /{skill}"
|
|
239
|
+
detail = f"Executing slash command /{skill}"
|
|
240
|
+
|
|
241
|
+
elif tool_name == "Task":
|
|
242
|
+
desc = input_data.get("description", "task")
|
|
243
|
+
short = f"Spawn agent: {desc[:30]}"
|
|
244
|
+
detail = f"Launching sub-agent: {desc}"
|
|
245
|
+
|
|
246
|
+
elif tool_name == "TodoWrite":
|
|
247
|
+
todos = input_data.get("todos", [])
|
|
248
|
+
count = len(todos) if isinstance(todos, list) else 0
|
|
249
|
+
short = f"Update todo: {count} items"
|
|
250
|
+
detail = f"Managing task list with {count} items"
|
|
251
|
+
|
|
252
|
+
elif tool_name.startswith("mcp__"):
|
|
253
|
+
parts = tool_name.split("__")
|
|
254
|
+
server = parts[1] if len(parts) > 1 else "unknown"
|
|
255
|
+
tool = parts[2] if len(parts) > 2 else tool_name
|
|
256
|
+
short = f"MCP: {server}/{tool}"
|
|
257
|
+
detail = f"Calling {tool} from MCP server {server}"
|
|
258
|
+
|
|
259
|
+
else:
|
|
260
|
+
short = f"Tool: {tool_name}"
|
|
261
|
+
detail = f"Using tool {tool_name}"
|
|
262
|
+
|
|
263
|
+
if not success:
|
|
264
|
+
short = f"[FAILED] {short}"
|
|
265
|
+
detail = f"[FAILED] {detail}"
|
|
266
|
+
|
|
267
|
+
return short, detail
|
|
268
|
+
|
|
269
|
+
|
|
256
270
|
def main():
|
|
257
271
|
"""Process PostToolUse hook."""
|
|
258
272
|
try:
|
|
@@ -288,6 +302,9 @@ def main():
|
|
|
288
302
|
db_path = get_db_path()
|
|
289
303
|
conn = ensure_database(db_path)
|
|
290
304
|
|
|
305
|
+
# Ensure analytics columns exist
|
|
306
|
+
ensure_analytics_columns(conn)
|
|
307
|
+
|
|
291
308
|
# Get or create session (auto-manages session lifecycle)
|
|
292
309
|
session_id = get_or_create_session(conn, project_path)
|
|
293
310
|
|
|
@@ -295,14 +312,36 @@ def main():
|
|
|
295
312
|
safe_input = redact_sensitive_fields(tool_input) if isinstance(tool_input, dict) else tool_input
|
|
296
313
|
safe_output = redact_sensitive_fields(tool_output) if isinstance(tool_output, dict) else tool_output
|
|
297
314
|
|
|
298
|
-
#
|
|
315
|
+
# Extract command analytics
|
|
316
|
+
skill_name = None
|
|
317
|
+
command_scope = None
|
|
318
|
+
mcp_server = None
|
|
319
|
+
|
|
320
|
+
# Extract skill info from Skill tool calls
|
|
321
|
+
if tool_name == "Skill" and isinstance(tool_input, dict):
|
|
322
|
+
skill_name, command_scope = extract_skill_info(tool_input, project_path)
|
|
323
|
+
|
|
324
|
+
# Extract MCP server from tool name (mcp__servername__toolname pattern)
|
|
325
|
+
if tool_name and tool_name.startswith("mcp__"):
|
|
326
|
+
mcp_server = extract_mcp_server(tool_name)
|
|
327
|
+
|
|
328
|
+
# Generate summary for activity
|
|
329
|
+
summary = None
|
|
330
|
+
summary_detail = None
|
|
331
|
+
try:
|
|
332
|
+
summary, summary_detail = generate_summary(tool_name, safe_input, not is_error)
|
|
333
|
+
except Exception:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
# Insert activity record with analytics columns
|
|
299
337
|
cursor = conn.cursor()
|
|
300
338
|
cursor.execute(
|
|
301
339
|
"""
|
|
302
340
|
INSERT INTO activities (
|
|
303
341
|
id, session_id, agent_id, timestamp, event_type,
|
|
304
|
-
tool_name, tool_input, tool_output, success, error_message, project_path
|
|
305
|
-
|
|
342
|
+
tool_name, tool_input, tool_output, success, error_message, project_path,
|
|
343
|
+
skill_name, command_scope, mcp_server, summary, summary_detail
|
|
344
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
306
345
|
""",
|
|
307
346
|
(
|
|
308
347
|
generate_id(),
|
|
@@ -316,6 +355,11 @@ def main():
|
|
|
316
355
|
0 if is_error else 1,
|
|
317
356
|
error_message,
|
|
318
357
|
project_path,
|
|
358
|
+
skill_name,
|
|
359
|
+
command_scope,
|
|
360
|
+
mcp_server,
|
|
361
|
+
summary,
|
|
362
|
+
summary_detail,
|
|
319
363
|
),
|
|
320
364
|
)
|
|
321
365
|
conn.commit()
|
{omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/hooks/pre_tool_use.py
RENAMED
|
@@ -22,14 +22,12 @@ import re
|
|
|
22
22
|
import sys
|
|
23
23
|
import os
|
|
24
24
|
import sqlite3
|
|
25
|
-
import time
|
|
26
25
|
from datetime import datetime, timezone
|
|
27
26
|
from pathlib import Path
|
|
28
|
-
from typing import Optional
|
|
29
27
|
|
|
28
|
+
# Import shared session management
|
|
29
|
+
from session_utils import get_or_create_session
|
|
30
30
|
|
|
31
|
-
# Session timeout in seconds (4 hours of inactivity = new session)
|
|
32
|
-
SESSION_TIMEOUT_SECONDS = 4 * 60 * 60
|
|
33
31
|
|
|
34
32
|
# Patterns for sensitive field names that should be redacted
|
|
35
33
|
SENSITIVE_FIELD_PATTERNS = [
|
|
@@ -41,128 +39,6 @@ SENSITIVE_FIELD_PATTERNS = [
|
|
|
41
39
|
]
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
def generate_session_id() -> str:
|
|
45
|
-
"""Generate a unique session ID matching the MCP format."""
|
|
46
|
-
timestamp_ms = int(time.time() * 1000)
|
|
47
|
-
random_hex = os.urandom(4).hex()
|
|
48
|
-
return f"sess_{timestamp_ms}_{random_hex}"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def get_session_file_path() -> Path:
|
|
52
|
-
"""Get the path to the current session file."""
|
|
53
|
-
project_path = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
|
|
54
|
-
return Path(project_path) / ".omni-cortex" / "current_session.json"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def load_session_file() -> Optional[dict]:
|
|
58
|
-
"""Load the current session from file if it exists and is valid."""
|
|
59
|
-
session_file = get_session_file_path()
|
|
60
|
-
if not session_file.exists():
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
try:
|
|
64
|
-
with open(session_file, "r") as f:
|
|
65
|
-
return json.load(f)
|
|
66
|
-
except (json.JSONDecodeError, IOError):
|
|
67
|
-
return None
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def save_session_file(session_data: dict) -> None:
|
|
71
|
-
"""Save the current session to file."""
|
|
72
|
-
session_file = get_session_file_path()
|
|
73
|
-
session_file.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
-
|
|
75
|
-
with open(session_file, "w") as f:
|
|
76
|
-
json.dump(session_data, f, indent=2)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def is_session_valid(session_data: dict) -> bool:
|
|
80
|
-
"""Check if a session is still valid (not timed out)."""
|
|
81
|
-
last_activity = session_data.get("last_activity_at")
|
|
82
|
-
if not last_activity:
|
|
83
|
-
return False
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
last_time = datetime.fromisoformat(last_activity.replace("Z", "+00:00"))
|
|
87
|
-
now = datetime.now(timezone.utc)
|
|
88
|
-
elapsed_seconds = (now - last_time).total_seconds()
|
|
89
|
-
return elapsed_seconds < SESSION_TIMEOUT_SECONDS
|
|
90
|
-
except (ValueError, TypeError):
|
|
91
|
-
return False
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def create_session_in_db(conn: sqlite3.Connection, session_id: str, project_path: str) -> None:
|
|
95
|
-
"""Create a new session record in the database."""
|
|
96
|
-
cursor = conn.cursor()
|
|
97
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
98
|
-
|
|
99
|
-
# Check if sessions table exists (it might not if only activities table was created)
|
|
100
|
-
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'")
|
|
101
|
-
if cursor.fetchone() is None:
|
|
102
|
-
# Create sessions table with minimal schema
|
|
103
|
-
conn.executescript("""
|
|
104
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
105
|
-
id TEXT PRIMARY KEY,
|
|
106
|
-
project_path TEXT NOT NULL,
|
|
107
|
-
started_at TEXT NOT NULL,
|
|
108
|
-
ended_at TEXT,
|
|
109
|
-
summary TEXT,
|
|
110
|
-
tags TEXT,
|
|
111
|
-
metadata TEXT
|
|
112
|
-
);
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at DESC);
|
|
114
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_path);
|
|
115
|
-
""")
|
|
116
|
-
conn.commit()
|
|
117
|
-
|
|
118
|
-
cursor.execute(
|
|
119
|
-
"""
|
|
120
|
-
INSERT OR IGNORE INTO sessions (id, project_path, started_at)
|
|
121
|
-
VALUES (?, ?, ?)
|
|
122
|
-
""",
|
|
123
|
-
(session_id, project_path, now),
|
|
124
|
-
)
|
|
125
|
-
conn.commit()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def get_or_create_session(conn: sqlite3.Connection, project_path: str) -> str:
|
|
129
|
-
"""Get the current session ID, creating a new one if needed.
|
|
130
|
-
|
|
131
|
-
Session management logic:
|
|
132
|
-
1. Check for existing session file
|
|
133
|
-
2. If exists and not timed out, use it and update last_activity
|
|
134
|
-
3. If doesn't exist or timed out, create new session
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
The session ID to use for activity logging
|
|
138
|
-
"""
|
|
139
|
-
session_data = load_session_file()
|
|
140
|
-
now_iso = datetime.now(timezone.utc).isoformat()
|
|
141
|
-
|
|
142
|
-
if session_data and is_session_valid(session_data):
|
|
143
|
-
# Update last activity time
|
|
144
|
-
session_data["last_activity_at"] = now_iso
|
|
145
|
-
save_session_file(session_data)
|
|
146
|
-
return session_data["session_id"]
|
|
147
|
-
|
|
148
|
-
# Create new session
|
|
149
|
-
session_id = generate_session_id()
|
|
150
|
-
|
|
151
|
-
# Create in database
|
|
152
|
-
create_session_in_db(conn, session_id, project_path)
|
|
153
|
-
|
|
154
|
-
# Save to file
|
|
155
|
-
session_data = {
|
|
156
|
-
"session_id": session_id,
|
|
157
|
-
"project_path": project_path,
|
|
158
|
-
"started_at": now_iso,
|
|
159
|
-
"last_activity_at": now_iso,
|
|
160
|
-
}
|
|
161
|
-
save_session_file(session_data)
|
|
162
|
-
|
|
163
|
-
return session_id
|
|
164
|
-
|
|
165
|
-
|
|
166
42
|
def redact_sensitive_fields(data: dict) -> dict:
|
|
167
43
|
"""Redact sensitive fields from a dictionary for safe logging.
|
|
168
44
|
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Shared session management utilities for Claude Code hooks.
|
|
3
|
+
|
|
4
|
+
This module provides session management functionality that can be shared
|
|
5
|
+
across pre_tool_use.py and post_tool_use.py hooks to ensure consistent
|
|
6
|
+
session tracking.
|
|
7
|
+
|
|
8
|
+
Session Management Logic:
|
|
9
|
+
1. Check for existing session file at `.omni-cortex/current_session.json`
|
|
10
|
+
2. If session exists and is valid (not timed out), use it
|
|
11
|
+
3. If no valid session, create a new one in both file and database
|
|
12
|
+
4. Update last_activity_at on each use to track session activity
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sqlite3
|
|
18
|
+
import time
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Session timeout in seconds (4 hours of inactivity = new session)
|
|
25
|
+
SESSION_TIMEOUT_SECONDS = 4 * 60 * 60
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def generate_session_id() -> str:
|
|
29
|
+
"""Generate a unique session ID matching the MCP format.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Session ID in format: sess_{timestamp_ms}_{random_hex}
|
|
33
|
+
"""
|
|
34
|
+
timestamp_ms = int(time.time() * 1000)
|
|
35
|
+
random_hex = os.urandom(4).hex()
|
|
36
|
+
return f"sess_{timestamp_ms}_{random_hex}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_session_file_path() -> Path:
|
|
40
|
+
"""Get the path to the current session file.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Path to .omni-cortex/current_session.json
|
|
44
|
+
"""
|
|
45
|
+
project_path = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
|
|
46
|
+
return Path(project_path) / ".omni-cortex" / "current_session.json"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_session_file() -> Optional[dict]:
|
|
50
|
+
"""Load the current session from file if it exists.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Session data dict or None if file doesn't exist or is invalid
|
|
54
|
+
"""
|
|
55
|
+
session_file = get_session_file_path()
|
|
56
|
+
if not session_file.exists():
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
with open(session_file, "r") as f:
|
|
61
|
+
return json.load(f)
|
|
62
|
+
except (json.JSONDecodeError, IOError):
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def save_session_file(session_data: dict) -> None:
|
|
67
|
+
"""Save the current session to file.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
session_data: Dict containing session_id, project_path, started_at, last_activity_at
|
|
71
|
+
"""
|
|
72
|
+
session_file = get_session_file_path()
|
|
73
|
+
session_file.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
with open(session_file, "w") as f:
|
|
76
|
+
json.dump(session_data, f, indent=2)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_session_valid(session_data: dict) -> bool:
|
|
80
|
+
"""Check if a session is still valid (not timed out).
|
|
81
|
+
|
|
82
|
+
A session is valid if:
|
|
83
|
+
- It has a last_activity_at timestamp
|
|
84
|
+
- The timestamp is within SESSION_TIMEOUT_SECONDS of now
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
session_data: Session dict with last_activity_at field
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if session is valid, False otherwise
|
|
91
|
+
"""
|
|
92
|
+
last_activity = session_data.get("last_activity_at")
|
|
93
|
+
if not last_activity:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
last_time = datetime.fromisoformat(last_activity.replace("Z", "+00:00"))
|
|
98
|
+
now = datetime.now(timezone.utc)
|
|
99
|
+
elapsed_seconds = (now - last_time).total_seconds()
|
|
100
|
+
return elapsed_seconds < SESSION_TIMEOUT_SECONDS
|
|
101
|
+
except (ValueError, TypeError):
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def create_session_in_db(conn: sqlite3.Connection, session_id: str, project_path: str) -> None:
|
|
106
|
+
"""Create a new session record in the database.
|
|
107
|
+
|
|
108
|
+
Also creates the sessions table if it doesn't exist (for first-run scenarios).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
conn: SQLite database connection
|
|
112
|
+
session_id: The session ID to create
|
|
113
|
+
project_path: The project directory path
|
|
114
|
+
"""
|
|
115
|
+
cursor = conn.cursor()
|
|
116
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
117
|
+
|
|
118
|
+
# Check if sessions table exists (it might not if only activities table was created)
|
|
119
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'")
|
|
120
|
+
if cursor.fetchone() is None:
|
|
121
|
+
# Create sessions table with minimal schema
|
|
122
|
+
conn.executescript("""
|
|
123
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
124
|
+
id TEXT PRIMARY KEY,
|
|
125
|
+
project_path TEXT NOT NULL,
|
|
126
|
+
started_at TEXT NOT NULL,
|
|
127
|
+
ended_at TEXT,
|
|
128
|
+
summary TEXT,
|
|
129
|
+
tags TEXT,
|
|
130
|
+
metadata TEXT
|
|
131
|
+
);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at DESC);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_path);
|
|
134
|
+
""")
|
|
135
|
+
conn.commit()
|
|
136
|
+
|
|
137
|
+
cursor.execute(
|
|
138
|
+
"""
|
|
139
|
+
INSERT OR IGNORE INTO sessions (id, project_path, started_at)
|
|
140
|
+
VALUES (?, ?, ?)
|
|
141
|
+
""",
|
|
142
|
+
(session_id, project_path, now),
|
|
143
|
+
)
|
|
144
|
+
conn.commit()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_or_create_session(conn: sqlite3.Connection, project_path: str) -> str:
|
|
148
|
+
"""Get the current session ID, creating a new one if needed.
|
|
149
|
+
|
|
150
|
+
Session management logic:
|
|
151
|
+
1. Check for existing session file
|
|
152
|
+
2. If exists and not timed out, use it and update last_activity
|
|
153
|
+
3. If doesn't exist or timed out, create new session
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
conn: SQLite database connection
|
|
157
|
+
project_path: The project directory path
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
The session ID to use for activity logging
|
|
161
|
+
"""
|
|
162
|
+
session_data = load_session_file()
|
|
163
|
+
now_iso = datetime.now(timezone.utc).isoformat()
|
|
164
|
+
|
|
165
|
+
if session_data and is_session_valid(session_data):
|
|
166
|
+
# Update last activity time
|
|
167
|
+
session_data["last_activity_at"] = now_iso
|
|
168
|
+
save_session_file(session_data)
|
|
169
|
+
return session_data["session_id"]
|
|
170
|
+
|
|
171
|
+
# Create new session
|
|
172
|
+
session_id = generate_session_id()
|
|
173
|
+
|
|
174
|
+
# Create in database
|
|
175
|
+
create_session_in_db(conn, session_id, project_path)
|
|
176
|
+
|
|
177
|
+
# Save to file
|
|
178
|
+
session_data = {
|
|
179
|
+
"session_id": session_id,
|
|
180
|
+
"project_path": project_path,
|
|
181
|
+
"started_at": now_iso,
|
|
182
|
+
"last_activity_at": now_iso,
|
|
183
|
+
}
|
|
184
|
+
save_session_file(session_data)
|
|
185
|
+
|
|
186
|
+
return session_id
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omni-cortex
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Give Claude Code a perfect memory - auto-logs everything, searches smartly, and gets smarter over time
|
|
5
5
|
Project-URL: Homepage, https://github.com/AllCytes/Omni-Cortex
|
|
6
6
|
Project-URL: Repository, https://github.com/AllCytes/Omni-Cortex
|
|
@@ -294,18 +294,47 @@ The PDFs use a light theme with blue/purple/green accents. Edit `docs/create_pdf
|
|
|
294
294
|
|
|
295
295
|
## Development
|
|
296
296
|
|
|
297
|
+
### Quick Setup (with Claude Code)
|
|
298
|
+
|
|
299
|
+
If you're using Claude Code, just run:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
/dev-setup
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
This will guide you through setting up the development environment.
|
|
306
|
+
|
|
307
|
+
### Manual Setup
|
|
308
|
+
|
|
297
309
|
```bash
|
|
298
|
-
#
|
|
299
|
-
|
|
310
|
+
# Clone and install in editable mode
|
|
311
|
+
git clone https://github.com/AllCytes/Omni-Cortex.git
|
|
312
|
+
cd Omni-Cortex
|
|
313
|
+
pip install -e .
|
|
314
|
+
|
|
315
|
+
# Install dashboard dependencies
|
|
316
|
+
cd dashboard/backend && pip install -r requirements.txt
|
|
317
|
+
cd ../frontend && npm install
|
|
318
|
+
cd ../..
|
|
319
|
+
|
|
320
|
+
# Verify installation
|
|
321
|
+
omni-cortex --help
|
|
322
|
+
omni-cortex-dashboard --help
|
|
323
|
+
```
|
|
300
324
|
|
|
301
|
-
|
|
325
|
+
**Important**: Always use `pip install -e .` (editable mode) so changes are immediately reflected without reinstalling.
|
|
326
|
+
|
|
327
|
+
### Running Tests
|
|
328
|
+
|
|
329
|
+
```bash
|
|
302
330
|
pytest
|
|
303
331
|
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
ruff check src tests
|
|
332
|
+
# With coverage
|
|
333
|
+
pytest --cov=src/omni_cortex
|
|
307
334
|
```
|
|
308
335
|
|
|
336
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for full development guidelines.
|
|
337
|
+
|
|
309
338
|
## Security
|
|
310
339
|
|
|
311
340
|
Omni Cortex v1.0.3 has been security reviewed:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/hooks/post_tool_use.py,sha256=yBLoYvEdUunbG8fclzIEWszCptworkUUcOUSKzNsvms,12271
|
|
2
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/hooks/pre_tool_use.py,sha256=mkZ7eeBnjWkIgNnrqfYSXIhhLNYYk4hQx_6F0pNrGoc,6395
|
|
3
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/hooks/session_utils.py,sha256=3SKPCytqWuRPOupWdzmwBoKBDJqtLcT1Nle_pueDQUY,5746
|
|
4
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/hooks/stop.py,sha256=T1bwcmbTLj0gzjrVvFBT1zB6wff4J2YkYBAY-ZxZI5g,5336
|
|
5
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/hooks/subagent_stop.py,sha256=V9HQSFGNOfkg8ZCstPEy4h5V8BP4AbrVr8teFzN1kNk,3314
|
|
6
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/.env.example,sha256=LenAe1A9dnwaS5UaRFPR0m_dghUcjsK-yMXZTSLIL8o,667
|
|
7
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/backfill_summaries.py,sha256=ElchfcBv4pmVr2PsePCgFlCyuvf4_jDJj_C3AmMhu7U,8973
|
|
8
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/chat_service.py,sha256=2Dfxlj0xwR47EI1jrbOF8t1E0m_OaXl-wdJoo6rqiHE,9187
|
|
9
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/database.py,sha256=Zh4Hl5I0DTZCosWIvkOYpo3Nyta8f54vnj72giDQ8vE,34942
|
|
10
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/image_service.py,sha256=wkWjUtLTLqIFgJ9WU99O8IWDvEUbv9-P8pizkV-TvhM,19059
|
|
11
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/logging_config.py,sha256=WnunFGET9zlsn9WBpVsio2zI7BiUQanE0xzAQQxIhII,3944
|
|
12
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/main.py,sha256=-sJg8GIOzX07xuUxvE6cZ6THqLblA0ObxQiOyNfvrFk,37232
|
|
13
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/models.py,sha256=Lv_qIrDNRlQNiveRwDrlhVz1QTeWD4DPpr5BBuA5Ty0,5968
|
|
14
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/project_config.py,sha256=ZxGoeRpHvN5qQyf2hRxrAZiHrPSwdQp59f0di6O1LKM,4352
|
|
15
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/project_scanner.py,sha256=lwFXS8iJbOoxf7FAyo2TjH25neaMHiJ8B3jS57XxtDI,5713
|
|
16
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/prompt_security.py,sha256=LcdZhYy1CfpSq_4BPO6lMJ15phc2ZXLUSBAnAvODVCI,3423
|
|
17
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/pyproject.toml,sha256=9pbbGQXLe1Xd06nZAtDySCHIlfMWvPaB-C6tGZR6umc,502
|
|
18
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/security.py,sha256=nQsoPE0n5dtY9ive00d33W1gL48GgK7C5Ae0BK2oW2k,3479
|
|
19
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/uv.lock,sha256=miB9zGGSirBkjDE-OZTPCnv43Yc98xuAz_Ne8vTNFHg,186004
|
|
20
|
+
omni_cortex-1.7.0.data/data/share/omni-cortex/dashboard/backend/websocket_manager.py,sha256=ABXAtlhBI5vnTcwdQUS-UQcDyTn-rWZL5OKEP9YY-kU,3619
|
|
21
|
+
omni_cortex-1.7.0.dist-info/METADATA,sha256=Oij28FeAaP2iylNabfycTMGdR2ynLdhia8T0OGB_GAc,10521
|
|
22
|
+
omni_cortex-1.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
23
|
+
omni_cortex-1.7.0.dist-info/entry_points.txt,sha256=rohx4mFH2ffZmMb9QXPZmFf-ZGjA3jpKVDVeET-ttiM,150
|
|
24
|
+
omni_cortex-1.7.0.dist-info/licenses/LICENSE,sha256=oG_397owMmi-Umxp5sYocJ6RPohp9_bDNnnEu9OUphg,1072
|
|
25
|
+
omni_cortex-1.7.0.dist-info/RECORD,,
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/hooks/post_tool_use.py,sha256=aHaT_Q4_6mLYiYEqppAjQlQ5HqU13MUHq3g8T5agZjI,10779
|
|
2
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/hooks/pre_tool_use.py,sha256=4WrzuJHeyafGM0e1mp3MzPot_mPL4ZBFQOZBw_dow4E,10464
|
|
3
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/hooks/stop.py,sha256=T1bwcmbTLj0gzjrVvFBT1zB6wff4J2YkYBAY-ZxZI5g,5336
|
|
4
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/hooks/subagent_stop.py,sha256=V9HQSFGNOfkg8ZCstPEy4h5V8BP4AbrVr8teFzN1kNk,3314
|
|
5
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/.env.example,sha256=LenAe1A9dnwaS5UaRFPR0m_dghUcjsK-yMXZTSLIL8o,667
|
|
6
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/backfill_summaries.py,sha256=ElchfcBv4pmVr2PsePCgFlCyuvf4_jDJj_C3AmMhu7U,8973
|
|
7
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/chat_service.py,sha256=2Dfxlj0xwR47EI1jrbOF8t1E0m_OaXl-wdJoo6rqiHE,9187
|
|
8
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/database.py,sha256=Zh4Hl5I0DTZCosWIvkOYpo3Nyta8f54vnj72giDQ8vE,34942
|
|
9
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/image_service.py,sha256=wkWjUtLTLqIFgJ9WU99O8IWDvEUbv9-P8pizkV-TvhM,19059
|
|
10
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/logging_config.py,sha256=WnunFGET9zlsn9WBpVsio2zI7BiUQanE0xzAQQxIhII,3944
|
|
11
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/main.py,sha256=-sJg8GIOzX07xuUxvE6cZ6THqLblA0ObxQiOyNfvrFk,37232
|
|
12
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/models.py,sha256=Lv_qIrDNRlQNiveRwDrlhVz1QTeWD4DPpr5BBuA5Ty0,5968
|
|
13
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/project_config.py,sha256=ZxGoeRpHvN5qQyf2hRxrAZiHrPSwdQp59f0di6O1LKM,4352
|
|
14
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/project_scanner.py,sha256=lwFXS8iJbOoxf7FAyo2TjH25neaMHiJ8B3jS57XxtDI,5713
|
|
15
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/prompt_security.py,sha256=LcdZhYy1CfpSq_4BPO6lMJ15phc2ZXLUSBAnAvODVCI,3423
|
|
16
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/pyproject.toml,sha256=9pbbGQXLe1Xd06nZAtDySCHIlfMWvPaB-C6tGZR6umc,502
|
|
17
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/security.py,sha256=nQsoPE0n5dtY9ive00d33W1gL48GgK7C5Ae0BK2oW2k,3479
|
|
18
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/uv.lock,sha256=miB9zGGSirBkjDE-OZTPCnv43Yc98xuAz_Ne8vTNFHg,186004
|
|
19
|
-
omni_cortex-1.6.0.data/data/share/omni-cortex/dashboard/backend/websocket_manager.py,sha256=ABXAtlhBI5vnTcwdQUS-UQcDyTn-rWZL5OKEP9YY-kU,3619
|
|
20
|
-
omni_cortex-1.6.0.dist-info/METADATA,sha256=ZCe4O8ZALiljSLbQA5JQ8Y6tm8XwwArKnX-v4dEK44Y,9855
|
|
21
|
-
omni_cortex-1.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
22
|
-
omni_cortex-1.6.0.dist-info/entry_points.txt,sha256=rohx4mFH2ffZmMb9QXPZmFf-ZGjA3jpKVDVeET-ttiM,150
|
|
23
|
-
omni_cortex-1.6.0.dist-info/licenses/LICENSE,sha256=oG_397owMmi-Umxp5sYocJ6RPohp9_bDNnnEu9OUphg,1072
|
|
24
|
-
omni_cortex-1.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/main.py
RENAMED
|
File without changes
|
{omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/dashboard/backend/uv.lock
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{omni_cortex-1.6.0.data → omni_cortex-1.7.0.data}/data/share/omni-cortex/hooks/subagent_stop.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|