omni-cortex 1.17.1__py3-none-any.whl → 1.17.3__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/__init__.py +3 -0
- omni_cortex/_bundled/dashboard/backend/.env.example +12 -0
- omni_cortex/_bundled/dashboard/backend/backfill_summaries.py +280 -0
- omni_cortex/_bundled/dashboard/backend/chat_service.py +631 -0
- omni_cortex/_bundled/dashboard/backend/database.py +1773 -0
- omni_cortex/_bundled/dashboard/backend/image_service.py +552 -0
- omni_cortex/_bundled/dashboard/backend/logging_config.py +122 -0
- omni_cortex/_bundled/dashboard/backend/main.py +1888 -0
- omni_cortex/_bundled/dashboard/backend/models.py +472 -0
- omni_cortex/_bundled/dashboard/backend/project_config.py +170 -0
- omni_cortex/_bundled/dashboard/backend/project_scanner.py +164 -0
- omni_cortex/_bundled/dashboard/backend/prompt_security.py +111 -0
- omni_cortex/_bundled/dashboard/backend/pyproject.toml +23 -0
- omni_cortex/_bundled/dashboard/backend/security.py +104 -0
- omni_cortex/_bundled/dashboard/backend/uv.lock +1110 -0
- omni_cortex/_bundled/dashboard/backend/websocket_manager.py +104 -0
- omni_cortex/_bundled/hooks/post_tool_use.py +497 -0
- omni_cortex/_bundled/hooks/pre_tool_use.py +277 -0
- omni_cortex/_bundled/hooks/session_utils.py +186 -0
- omni_cortex/_bundled/hooks/stop.py +219 -0
- omni_cortex/_bundled/hooks/subagent_stop.py +120 -0
- omni_cortex/_bundled/hooks/user_prompt.py +220 -0
- omni_cortex/categorization/__init__.py +9 -0
- omni_cortex/categorization/auto_tags.py +166 -0
- omni_cortex/categorization/auto_type.py +165 -0
- omni_cortex/config.py +141 -0
- omni_cortex/dashboard.py +238 -0
- omni_cortex/database/__init__.py +24 -0
- omni_cortex/database/connection.py +137 -0
- omni_cortex/database/migrations.py +210 -0
- omni_cortex/database/schema.py +212 -0
- omni_cortex/database/sync.py +421 -0
- omni_cortex/decay/__init__.py +7 -0
- omni_cortex/decay/importance.py +147 -0
- omni_cortex/embeddings/__init__.py +35 -0
- omni_cortex/embeddings/local.py +442 -0
- omni_cortex/models/__init__.py +20 -0
- omni_cortex/models/activity.py +265 -0
- omni_cortex/models/agent.py +144 -0
- omni_cortex/models/memory.py +395 -0
- omni_cortex/models/relationship.py +206 -0
- omni_cortex/models/session.py +290 -0
- omni_cortex/resources/__init__.py +1 -0
- omni_cortex/search/__init__.py +22 -0
- omni_cortex/search/hybrid.py +197 -0
- omni_cortex/search/keyword.py +204 -0
- omni_cortex/search/ranking.py +127 -0
- omni_cortex/search/semantic.py +232 -0
- omni_cortex/server.py +360 -0
- omni_cortex/setup.py +284 -0
- omni_cortex/tools/__init__.py +13 -0
- omni_cortex/tools/activities.py +453 -0
- omni_cortex/tools/memories.py +536 -0
- omni_cortex/tools/sessions.py +311 -0
- omni_cortex/tools/utilities.py +477 -0
- omni_cortex/utils/__init__.py +13 -0
- omni_cortex/utils/formatting.py +282 -0
- omni_cortex/utils/ids.py +72 -0
- omni_cortex/utils/timestamps.py +129 -0
- omni_cortex/utils/truncation.py +111 -0
- {omni_cortex-1.17.1.dist-info → omni_cortex-1.17.3.dist-info}/METADATA +1 -1
- omni_cortex-1.17.3.dist-info/RECORD +86 -0
- omni_cortex-1.17.1.dist-info/RECORD +0 -26
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/.env.example +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/backfill_summaries.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/chat_service.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/database.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/image_service.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/logging_config.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/main.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/models.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/project_config.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/project_scanner.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/prompt_security.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/pyproject.toml +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/security.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/uv.lock +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/websocket_manager.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/post_tool_use.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/pre_tool_use.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/session_utils.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/stop.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/subagent_stop.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/user_prompt.py +0 -0
- {omni_cortex-1.17.1.dist-info → omni_cortex-1.17.3.dist-info}/WHEEL +0 -0
- {omni_cortex-1.17.1.dist-info → omni_cortex-1.17.3.dist-info}/entry_points.txt +0 -0
- {omni_cortex-1.17.1.dist-info → omni_cortex-1.17.3.dist-info}/licenses/LICENSE +0 -0
omni_cortex/config.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Configuration management for Omni Cortex."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class CortexConfig:
|
|
13
|
+
"""Configuration settings for Omni Cortex."""
|
|
14
|
+
|
|
15
|
+
# Database
|
|
16
|
+
schema_version: str = "1.0"
|
|
17
|
+
|
|
18
|
+
# Embedding (disabled by default - model loading can be slow)
|
|
19
|
+
embedding_model: str = "all-MiniLM-L6-v2"
|
|
20
|
+
embedding_enabled: bool = False
|
|
21
|
+
|
|
22
|
+
# Decay
|
|
23
|
+
decay_rate_per_day: float = 0.5
|
|
24
|
+
freshness_review_days: int = 30
|
|
25
|
+
|
|
26
|
+
# Output
|
|
27
|
+
max_output_truncation: int = 10000
|
|
28
|
+
max_tool_input_size: int = 10000
|
|
29
|
+
|
|
30
|
+
# Session
|
|
31
|
+
auto_provide_context: bool = True
|
|
32
|
+
context_depth: int = 3
|
|
33
|
+
|
|
34
|
+
# Search (default to keyword since embeddings are disabled by default)
|
|
35
|
+
default_search_mode: str = "keyword"
|
|
36
|
+
|
|
37
|
+
# Global
|
|
38
|
+
global_sync_enabled: bool = True
|
|
39
|
+
api_fallback_enabled: bool = False
|
|
40
|
+
api_key: str = ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_project_path() -> Path:
|
|
44
|
+
"""Get the current project path from environment or cwd."""
|
|
45
|
+
return Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_project_db_dir() -> Path:
|
|
49
|
+
"""Get the project-local .omni-cortex directory."""
|
|
50
|
+
return get_project_path() / ".omni-cortex"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_project_db_path() -> Path:
|
|
54
|
+
"""Get the path to the project database."""
|
|
55
|
+
return get_project_db_dir() / "cortex.db"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_global_db_dir() -> Path:
|
|
59
|
+
"""Get the global ~/.omni-cortex directory."""
|
|
60
|
+
return Path.home() / ".omni-cortex"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_global_db_path() -> Path:
|
|
64
|
+
"""Get the path to the global database."""
|
|
65
|
+
return get_global_db_dir() / "global.db"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_session_id() -> Optional[str]:
|
|
69
|
+
"""Get the current session ID from environment."""
|
|
70
|
+
return os.environ.get("CLAUDE_SESSION_ID")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def load_config(project_path: Optional[Path] = None) -> CortexConfig:
|
|
74
|
+
"""Load configuration from project and global config files.
|
|
75
|
+
|
|
76
|
+
Priority: project config > global config > defaults
|
|
77
|
+
"""
|
|
78
|
+
config = CortexConfig()
|
|
79
|
+
|
|
80
|
+
# Load global config
|
|
81
|
+
global_config_path = get_global_db_dir() / "config.yaml"
|
|
82
|
+
if global_config_path.exists():
|
|
83
|
+
try:
|
|
84
|
+
with open(global_config_path, "r") as f:
|
|
85
|
+
global_cfg = yaml.safe_load(f) or {}
|
|
86
|
+
_apply_config(config, global_cfg)
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
# Load project config
|
|
91
|
+
if project_path is None:
|
|
92
|
+
project_path = get_project_path()
|
|
93
|
+
project_config_path = project_path / ".omni-cortex" / "config.yaml"
|
|
94
|
+
if project_config_path.exists():
|
|
95
|
+
try:
|
|
96
|
+
with open(project_config_path, "r") as f:
|
|
97
|
+
project_cfg = yaml.safe_load(f) or {}
|
|
98
|
+
_apply_config(config, project_cfg)
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
return config
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _apply_config(config: CortexConfig, data: dict) -> None:
|
|
106
|
+
"""Apply configuration data to config object."""
|
|
107
|
+
for key, value in data.items():
|
|
108
|
+
if hasattr(config, key) and value is not None:
|
|
109
|
+
setattr(config, key, value)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def save_config(config: CortexConfig, project: bool = True) -> None:
|
|
113
|
+
"""Save configuration to file.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
config: Configuration to save
|
|
117
|
+
project: If True, save to project config; otherwise global
|
|
118
|
+
"""
|
|
119
|
+
if project:
|
|
120
|
+
config_dir = get_project_db_dir()
|
|
121
|
+
else:
|
|
122
|
+
config_dir = get_global_db_dir()
|
|
123
|
+
|
|
124
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
config_path = config_dir / "config.yaml"
|
|
126
|
+
|
|
127
|
+
data = {
|
|
128
|
+
"schema_version": config.schema_version,
|
|
129
|
+
"embedding_model": config.embedding_model,
|
|
130
|
+
"embedding_enabled": config.embedding_enabled,
|
|
131
|
+
"decay_rate_per_day": config.decay_rate_per_day,
|
|
132
|
+
"freshness_review_days": config.freshness_review_days,
|
|
133
|
+
"max_output_truncation": config.max_output_truncation,
|
|
134
|
+
"auto_provide_context": config.auto_provide_context,
|
|
135
|
+
"context_depth": config.context_depth,
|
|
136
|
+
"default_search_mode": config.default_search_mode,
|
|
137
|
+
"global_sync_enabled": config.global_sync_enabled,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
with open(config_path, "w") as f:
|
|
141
|
+
yaml.dump(data, f, default_flow_style=False)
|
omni_cortex/dashboard.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Dashboard CLI for Omni-Cortex.
|
|
2
|
+
|
|
3
|
+
Starts the web dashboard server for viewing and managing memories.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import webbrowser
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from time import sleep
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def check_editable_install() -> bool:
|
|
16
|
+
"""Check if package is installed in editable (development) mode.
|
|
17
|
+
|
|
18
|
+
Returns True if editable, False if installed from PyPI.
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
import importlib.metadata as metadata
|
|
22
|
+
dist = metadata.distribution("omni-cortex")
|
|
23
|
+
# Editable installs have a direct_url.json with editable=true
|
|
24
|
+
# or are installed via .egg-link
|
|
25
|
+
direct_url = dist.read_text("direct_url.json")
|
|
26
|
+
if direct_url and '"editable":true' in direct_url.replace(" ", ""):
|
|
27
|
+
return True
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# Alternative check: see if we're running from source directory
|
|
32
|
+
package_dir = Path(__file__).parent
|
|
33
|
+
repo_root = package_dir.parent.parent
|
|
34
|
+
if (repo_root / "pyproject.toml").exists() and (repo_root / ".git").exists():
|
|
35
|
+
# We're in a repo, check if there's an egg-link or editable marker
|
|
36
|
+
import site
|
|
37
|
+
for site_dir in [site.getusersitepackages()] + site.getsitepackages():
|
|
38
|
+
egg_link = Path(site_dir) / "omni-cortex.egg-link"
|
|
39
|
+
if egg_link.exists():
|
|
40
|
+
return True
|
|
41
|
+
# Check for __editable__ marker (PEP 660) - any version
|
|
42
|
+
for pth_file in Path(site_dir).glob("__editable__.omni_cortex*.pth"):
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def warn_non_editable_install() -> None:
|
|
49
|
+
"""Warn if not running in editable mode during development."""
|
|
50
|
+
if not check_editable_install():
|
|
51
|
+
# Check if we appear to be in a development context
|
|
52
|
+
package_dir = Path(__file__).parent
|
|
53
|
+
repo_root = package_dir.parent.parent
|
|
54
|
+
if (repo_root / "pyproject.toml").exists() and (repo_root / ".git").exists():
|
|
55
|
+
print("[Dashboard] Note: Package may not be in editable mode.")
|
|
56
|
+
print("[Dashboard] If you see import errors, run: pip install -e .")
|
|
57
|
+
print()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def find_dashboard_dir() -> Path | None:
|
|
61
|
+
"""Find the dashboard directory.
|
|
62
|
+
|
|
63
|
+
Searches in order:
|
|
64
|
+
1. Bundled inside package (works for user and system installs)
|
|
65
|
+
2. Development directory (cloned repo)
|
|
66
|
+
3. Package shared-data (installed via pip system-wide)
|
|
67
|
+
4. Site-packages share location
|
|
68
|
+
"""
|
|
69
|
+
package_dir = Path(__file__).parent
|
|
70
|
+
|
|
71
|
+
# Check for bundled dashboard inside package (most reliable for pip installs)
|
|
72
|
+
bundled_dashboard = package_dir / "_bundled" / "dashboard"
|
|
73
|
+
if bundled_dashboard.exists() and (bundled_dashboard / "backend" / "main.py").exists():
|
|
74
|
+
return bundled_dashboard
|
|
75
|
+
|
|
76
|
+
# Check for development directory (repo structure)
|
|
77
|
+
# Go up from src/omni_cortex to repo root, then dashboard
|
|
78
|
+
repo_root = package_dir.parent.parent
|
|
79
|
+
dashboard_in_repo = repo_root / "dashboard"
|
|
80
|
+
if dashboard_in_repo.exists() and (dashboard_in_repo / "backend" / "main.py").exists():
|
|
81
|
+
return dashboard_in_repo
|
|
82
|
+
|
|
83
|
+
# Check pip shared-data location (for backwards compatibility)
|
|
84
|
+
# On Unix: ~/.local/share/omni-cortex/dashboard
|
|
85
|
+
# On Windows: %APPDATA%/Python/share/omni-cortex/dashboard
|
|
86
|
+
import site
|
|
87
|
+
for site_dir in site.getsitepackages() + [site.getusersitepackages()]:
|
|
88
|
+
share_dir = Path(site_dir).parent / "share" / "omni-cortex" / "dashboard"
|
|
89
|
+
if share_dir.exists() and (share_dir / "backend" / "main.py").exists():
|
|
90
|
+
return share_dir
|
|
91
|
+
|
|
92
|
+
# Check relative to sys.prefix (virtualenv)
|
|
93
|
+
share_in_prefix = Path(sys.prefix) / "share" / "omni-cortex" / "dashboard"
|
|
94
|
+
if share_in_prefix.exists() and (share_in_prefix / "backend" / "main.py").exists():
|
|
95
|
+
return share_in_prefix
|
|
96
|
+
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def check_dependencies() -> bool:
|
|
101
|
+
"""Check if dashboard dependencies are installed."""
|
|
102
|
+
try:
|
|
103
|
+
import uvicorn # noqa: F401
|
|
104
|
+
import fastapi # noqa: F401
|
|
105
|
+
return True
|
|
106
|
+
except ImportError:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def install_dependencies() -> bool:
|
|
111
|
+
"""Install dashboard dependencies."""
|
|
112
|
+
required_packages = ["uvicorn", "fastapi"]
|
|
113
|
+
|
|
114
|
+
print("[Dashboard] Installing dependencies...")
|
|
115
|
+
try:
|
|
116
|
+
subprocess.check_call(
|
|
117
|
+
[sys.executable, "-m", "pip", "install", *required_packages, "-q"],
|
|
118
|
+
stdout=subprocess.DEVNULL,
|
|
119
|
+
stderr=subprocess.DEVNULL,
|
|
120
|
+
)
|
|
121
|
+
return True
|
|
122
|
+
except subprocess.CalledProcessError:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def start_server(dashboard_dir: Path, host: str, port: int, no_browser: bool) -> None:
|
|
127
|
+
"""Start the dashboard server."""
|
|
128
|
+
backend_dir = dashboard_dir / "backend"
|
|
129
|
+
|
|
130
|
+
# Add backend to path
|
|
131
|
+
sys.path.insert(0, str(backend_dir))
|
|
132
|
+
|
|
133
|
+
# Change to backend directory for relative imports
|
|
134
|
+
original_cwd = os.getcwd()
|
|
135
|
+
os.chdir(backend_dir)
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
import uvicorn
|
|
139
|
+
|
|
140
|
+
print(f"\n[Dashboard] Starting Omni-Cortex Dashboard")
|
|
141
|
+
print(f"[Dashboard] URL: http://{host}:{port}")
|
|
142
|
+
print(f"[Dashboard] API Docs: http://{host}:{port}/docs")
|
|
143
|
+
print(f"[Dashboard] Press Ctrl+C to stop\n")
|
|
144
|
+
|
|
145
|
+
# Open browser after short delay
|
|
146
|
+
if not no_browser:
|
|
147
|
+
def open_browser():
|
|
148
|
+
sleep(1.5)
|
|
149
|
+
webbrowser.open(f"http://{host}:{port}")
|
|
150
|
+
|
|
151
|
+
import threading
|
|
152
|
+
threading.Thread(target=open_browser, daemon=True).start()
|
|
153
|
+
|
|
154
|
+
# Run the server
|
|
155
|
+
uvicorn.run(
|
|
156
|
+
"main:app",
|
|
157
|
+
host=host,
|
|
158
|
+
port=port,
|
|
159
|
+
reload=False,
|
|
160
|
+
log_level="info",
|
|
161
|
+
)
|
|
162
|
+
finally:
|
|
163
|
+
os.chdir(original_cwd)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main():
|
|
167
|
+
"""Main entry point for omni-cortex dashboard command."""
|
|
168
|
+
# Check for potential editable install issues early
|
|
169
|
+
warn_non_editable_install()
|
|
170
|
+
|
|
171
|
+
parser = argparse.ArgumentParser(
|
|
172
|
+
description="Start the Omni-Cortex web dashboard",
|
|
173
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
174
|
+
epilog="""
|
|
175
|
+
Examples:
|
|
176
|
+
omni-cortex dashboard Start on default port 8765
|
|
177
|
+
omni-cortex dashboard --port 9000 Start on custom port
|
|
178
|
+
omni-cortex dashboard --no-browser Don't auto-open browser
|
|
179
|
+
"""
|
|
180
|
+
)
|
|
181
|
+
parser.add_argument(
|
|
182
|
+
"--host",
|
|
183
|
+
default="127.0.0.1",
|
|
184
|
+
help="Host to bind to (default: 127.0.0.1)"
|
|
185
|
+
)
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"--port", "-p",
|
|
188
|
+
type=int,
|
|
189
|
+
default=8765,
|
|
190
|
+
help="Port to run on (default: 8765)"
|
|
191
|
+
)
|
|
192
|
+
parser.add_argument(
|
|
193
|
+
"--no-browser",
|
|
194
|
+
action="store_true",
|
|
195
|
+
help="Don't automatically open browser"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
args = parser.parse_args()
|
|
199
|
+
|
|
200
|
+
# Find dashboard directory
|
|
201
|
+
dashboard_dir = find_dashboard_dir()
|
|
202
|
+
if not dashboard_dir:
|
|
203
|
+
print("[Dashboard] Error: Dashboard files not found.")
|
|
204
|
+
print("[Dashboard] If you installed via pip, try reinstalling:")
|
|
205
|
+
print(" pip install --force-reinstall omni-cortex")
|
|
206
|
+
print("\nOr clone the repository:")
|
|
207
|
+
print(" git clone https://github.com/AllCytes/Omni-Cortex.git")
|
|
208
|
+
sys.exit(1)
|
|
209
|
+
|
|
210
|
+
print(f"[Dashboard] Found dashboard at: {dashboard_dir}")
|
|
211
|
+
|
|
212
|
+
# Check/install dependencies
|
|
213
|
+
if not check_dependencies():
|
|
214
|
+
print("[Dashboard] Installing required dependencies...")
|
|
215
|
+
if not install_dependencies():
|
|
216
|
+
print("[Dashboard] Error: Failed to install dependencies.")
|
|
217
|
+
print("[Dashboard] Try manually: pip install uvicorn fastapi")
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
# Check if dist exists (built frontend)
|
|
221
|
+
dist_dir = dashboard_dir / "frontend" / "dist"
|
|
222
|
+
if not dist_dir.exists():
|
|
223
|
+
print(f"[Dashboard] Warning: Frontend not built ({dist_dir})")
|
|
224
|
+
print("[Dashboard] API will work but web UI may not be available.")
|
|
225
|
+
print("[Dashboard] To build: cd dashboard/frontend && npm install && npm run build")
|
|
226
|
+
|
|
227
|
+
# Start the server
|
|
228
|
+
try:
|
|
229
|
+
start_server(dashboard_dir, args.host, args.port, args.no_browser)
|
|
230
|
+
except KeyboardInterrupt:
|
|
231
|
+
print("\n[Dashboard] Stopped")
|
|
232
|
+
except Exception as e:
|
|
233
|
+
print(f"[Dashboard] Error: {e}")
|
|
234
|
+
sys.exit(1)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
if __name__ == "__main__":
|
|
238
|
+
main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Database layer for Omni Cortex - SQLite with FTS5."""
|
|
2
|
+
|
|
3
|
+
from .connection import get_connection, init_database, close_connection
|
|
4
|
+
from .schema import SCHEMA_VERSION, get_schema_sql
|
|
5
|
+
from .sync import (
|
|
6
|
+
sync_memory_to_global,
|
|
7
|
+
delete_memory_from_global,
|
|
8
|
+
search_global_memories,
|
|
9
|
+
get_global_stats,
|
|
10
|
+
sync_all_project_memories,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"get_connection",
|
|
15
|
+
"init_database",
|
|
16
|
+
"close_connection",
|
|
17
|
+
"SCHEMA_VERSION",
|
|
18
|
+
"get_schema_sql",
|
|
19
|
+
"sync_memory_to_global",
|
|
20
|
+
"delete_memory_from_global",
|
|
21
|
+
"search_global_memories",
|
|
22
|
+
"get_global_stats",
|
|
23
|
+
"sync_all_project_memories",
|
|
24
|
+
]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""SQLite connection management for Omni Cortex."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
import threading
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
|
|
9
|
+
from ..config import get_project_db_path, get_global_db_path, get_project_db_dir, get_global_db_dir
|
|
10
|
+
from .schema import get_schema_sql, SCHEMA_VERSION
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Thread-local storage for connections
|
|
14
|
+
_local = threading.local()
|
|
15
|
+
|
|
16
|
+
# Connection cache by path
|
|
17
|
+
_connections: dict[str, sqlite3.Connection] = {}
|
|
18
|
+
_lock = threading.Lock()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _configure_connection(conn: sqlite3.Connection) -> None:
|
|
22
|
+
"""Configure a SQLite connection with optimal settings."""
|
|
23
|
+
conn.row_factory = sqlite3.Row
|
|
24
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
|
25
|
+
conn.execute("PRAGMA journal_mode = WAL")
|
|
26
|
+
conn.execute("PRAGMA synchronous = NORMAL")
|
|
27
|
+
conn.execute("PRAGMA cache_size = -64000") # 64MB cache
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_connection(db_path: Optional[Path] = None, is_global: bool = False) -> sqlite3.Connection:
|
|
31
|
+
"""Get a database connection, creating it if necessary.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
db_path: Explicit path to database file
|
|
35
|
+
is_global: If True and no db_path, use global database
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
SQLite connection
|
|
39
|
+
"""
|
|
40
|
+
if db_path is None:
|
|
41
|
+
db_path = get_global_db_path() if is_global else get_project_db_path()
|
|
42
|
+
|
|
43
|
+
path_str = str(db_path)
|
|
44
|
+
|
|
45
|
+
with _lock:
|
|
46
|
+
if path_str not in _connections:
|
|
47
|
+
# Ensure directory exists
|
|
48
|
+
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
|
|
50
|
+
# Create connection
|
|
51
|
+
conn = sqlite3.connect(path_str, check_same_thread=False)
|
|
52
|
+
_configure_connection(conn)
|
|
53
|
+
_connections[path_str] = conn
|
|
54
|
+
|
|
55
|
+
return _connections[path_str]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def init_database(db_path: Optional[Path] = None, is_global: bool = False) -> sqlite3.Connection:
|
|
59
|
+
"""Initialize the database with schema.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
db_path: Explicit path to database file
|
|
63
|
+
is_global: If True and no db_path, use global database
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
SQLite connection
|
|
67
|
+
"""
|
|
68
|
+
if db_path is None:
|
|
69
|
+
if is_global:
|
|
70
|
+
db_path = get_global_db_path()
|
|
71
|
+
db_dir = get_global_db_dir()
|
|
72
|
+
else:
|
|
73
|
+
db_path = get_project_db_path()
|
|
74
|
+
db_dir = get_project_db_dir()
|
|
75
|
+
else:
|
|
76
|
+
db_dir = db_path.parent
|
|
77
|
+
|
|
78
|
+
# Ensure directory exists
|
|
79
|
+
db_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
|
|
81
|
+
conn = get_connection(db_path)
|
|
82
|
+
|
|
83
|
+
# Check if schema needs initialization
|
|
84
|
+
cursor = conn.cursor()
|
|
85
|
+
|
|
86
|
+
# Check if tables exist
|
|
87
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='memories'")
|
|
88
|
+
if cursor.fetchone() is None:
|
|
89
|
+
# Apply schema
|
|
90
|
+
conn.executescript(get_schema_sql())
|
|
91
|
+
|
|
92
|
+
# Record schema version
|
|
93
|
+
from ..utils.timestamps import now_iso
|
|
94
|
+
cursor.execute(
|
|
95
|
+
"INSERT OR REPLACE INTO schema_migrations (version, applied_at) VALUES (?, ?)",
|
|
96
|
+
(SCHEMA_VERSION, now_iso())
|
|
97
|
+
)
|
|
98
|
+
conn.commit()
|
|
99
|
+
|
|
100
|
+
return conn
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def close_connection(db_path: Optional[Path] = None, is_global: bool = False) -> None:
|
|
104
|
+
"""Close a database connection.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
db_path: Explicit path to database file
|
|
108
|
+
is_global: If True and no db_path, use global database
|
|
109
|
+
"""
|
|
110
|
+
if db_path is None:
|
|
111
|
+
db_path = get_global_db_path() if is_global else get_project_db_path()
|
|
112
|
+
|
|
113
|
+
path_str = str(db_path)
|
|
114
|
+
|
|
115
|
+
with _lock:
|
|
116
|
+
if path_str in _connections:
|
|
117
|
+
_connections[path_str].close()
|
|
118
|
+
del _connections[path_str]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def close_all_connections() -> None:
|
|
122
|
+
"""Close all database connections."""
|
|
123
|
+
with _lock:
|
|
124
|
+
for conn in _connections.values():
|
|
125
|
+
conn.close()
|
|
126
|
+
_connections.clear()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@contextmanager
|
|
130
|
+
def transaction(conn: sqlite3.Connection):
|
|
131
|
+
"""Context manager for database transactions."""
|
|
132
|
+
try:
|
|
133
|
+
yield conn
|
|
134
|
+
conn.commit()
|
|
135
|
+
except Exception:
|
|
136
|
+
conn.rollback()
|
|
137
|
+
raise
|