omni-cortex 1.0.7__tar.gz → 1.0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/PKG-INFO +1 -1
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/main.py +73 -1
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/models.py +25 -0
- omni_cortex-1.0.9/dashboard/backend/project_config.py +170 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/project_scanner.py +45 -22
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/pyproject.toml +1 -1
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/.gitignore +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/LICENSE +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/README.md +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/chat_service.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/database.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/logging_config.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/pyproject.toml +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/uv.lock +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/dashboard/backend/websocket_manager.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/hooks/post_tool_use.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/hooks/pre_tool_use.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/hooks/stop.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/hooks/subagent_stop.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/categorization/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/categorization/auto_tags.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/categorization/auto_type.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/config.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/dashboard.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/database/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/database/connection.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/database/migrations.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/database/schema.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/database/sync.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/decay/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/decay/importance.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/embeddings/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/embeddings/local.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/models/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/models/activity.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/models/agent.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/models/memory.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/models/relationship.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/models/session.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/resources/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/search/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/search/hybrid.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/search/keyword.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/search/ranking.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/search/semantic.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/server.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/setup.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/tools/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/tools/activities.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/tools/memories.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/tools/sessions.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/tools/utilities.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/utils/__init__.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/utils/formatting.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/utils/ids.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/utils/timestamps.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/omni_cortex/utils/truncation.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/scripts/import_ken_memories.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/scripts/populate_session_data.py +0 -0
- {omni_cortex-1.0.7 → omni_cortex-1.0.9}/scripts/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omni-cortex
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.9
|
|
4
4
|
Summary: Universal Memory MCP for Claude Code - dual-layer activity logging and knowledge storage
|
|
5
5
|
Project-URL: Homepage, https://github.com/AllCytes/Omni-Cortex
|
|
6
6
|
Project-URL: Repository, https://github.com/AllCytes/Omni-Cortex
|
|
@@ -39,7 +39,15 @@ from database import (
|
|
|
39
39
|
update_memory,
|
|
40
40
|
)
|
|
41
41
|
from logging_config import log_success, log_error
|
|
42
|
-
from models import ChatRequest, ChatResponse, FilterParams, MemoryUpdate, ProjectInfo
|
|
42
|
+
from models import ChatRequest, ChatResponse, FilterParams, MemoryUpdate, ProjectInfo, ProjectRegistration
|
|
43
|
+
from project_config import (
|
|
44
|
+
load_config,
|
|
45
|
+
add_registered_project,
|
|
46
|
+
remove_registered_project,
|
|
47
|
+
toggle_favorite,
|
|
48
|
+
add_scan_directory,
|
|
49
|
+
remove_scan_directory,
|
|
50
|
+
)
|
|
43
51
|
from project_scanner import scan_projects
|
|
44
52
|
from websocket_manager import manager
|
|
45
53
|
import chat_service
|
|
@@ -147,6 +155,70 @@ async def list_projects():
|
|
|
147
155
|
return scan_projects()
|
|
148
156
|
|
|
149
157
|
|
|
158
|
+
# --- Project Management Endpoints ---
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@app.get("/api/projects/config")
|
|
162
|
+
async def get_project_config():
|
|
163
|
+
"""Get project configuration (scan dirs, counts)."""
|
|
164
|
+
config = load_config()
|
|
165
|
+
return {
|
|
166
|
+
"scan_directories": config.scan_directories,
|
|
167
|
+
"registered_count": len(config.registered_projects),
|
|
168
|
+
"favorites_count": len(config.favorites),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@app.post("/api/projects/register")
|
|
173
|
+
async def register_project(body: ProjectRegistration):
|
|
174
|
+
"""Manually register a project by path."""
|
|
175
|
+
success = add_registered_project(body.path, body.display_name)
|
|
176
|
+
if not success:
|
|
177
|
+
raise HTTPException(400, "Invalid path or already registered")
|
|
178
|
+
return {"success": True}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@app.delete("/api/projects/register")
|
|
182
|
+
async def unregister_project(path: str = Query(..., description="Project path to unregister")):
|
|
183
|
+
"""Remove a registered project."""
|
|
184
|
+
success = remove_registered_project(path)
|
|
185
|
+
if not success:
|
|
186
|
+
raise HTTPException(404, "Project not found")
|
|
187
|
+
return {"success": True}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@app.post("/api/projects/favorite")
|
|
191
|
+
async def toggle_project_favorite(path: str = Query(..., description="Project path to toggle favorite")):
|
|
192
|
+
"""Toggle favorite status for a project."""
|
|
193
|
+
is_favorite = toggle_favorite(path)
|
|
194
|
+
return {"is_favorite": is_favorite}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@app.post("/api/projects/scan-directories")
|
|
198
|
+
async def add_scan_dir(directory: str = Query(..., description="Directory path to add")):
|
|
199
|
+
"""Add a directory to auto-scan list."""
|
|
200
|
+
success = add_scan_directory(directory)
|
|
201
|
+
if not success:
|
|
202
|
+
raise HTTPException(400, "Invalid directory or already added")
|
|
203
|
+
return {"success": True}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@app.delete("/api/projects/scan-directories")
|
|
207
|
+
async def remove_scan_dir(directory: str = Query(..., description="Directory path to remove")):
|
|
208
|
+
"""Remove a directory from auto-scan list."""
|
|
209
|
+
success = remove_scan_directory(directory)
|
|
210
|
+
if not success:
|
|
211
|
+
raise HTTPException(404, "Directory not found")
|
|
212
|
+
return {"success": True}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@app.post("/api/projects/refresh")
|
|
216
|
+
async def refresh_projects():
|
|
217
|
+
"""Force rescan of all project directories."""
|
|
218
|
+
projects = scan_projects()
|
|
219
|
+
return {"count": len(projects)}
|
|
220
|
+
|
|
221
|
+
|
|
150
222
|
@app.get("/api/memories")
|
|
151
223
|
async def list_memories(
|
|
152
224
|
project: str = Query(..., description="Path to the database file"),
|
|
@@ -15,6 +15,31 @@ class ProjectInfo(BaseModel):
|
|
|
15
15
|
last_modified: Optional[datetime] = None
|
|
16
16
|
memory_count: int = 0
|
|
17
17
|
is_global: bool = False
|
|
18
|
+
is_favorite: bool = False
|
|
19
|
+
is_registered: bool = False
|
|
20
|
+
display_name: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ScanDirectory(BaseModel):
|
|
24
|
+
"""A directory being scanned for projects."""
|
|
25
|
+
|
|
26
|
+
path: str
|
|
27
|
+
project_count: int = 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProjectRegistration(BaseModel):
|
|
31
|
+
"""Request to register a project."""
|
|
32
|
+
|
|
33
|
+
path: str
|
|
34
|
+
display_name: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ProjectConfigResponse(BaseModel):
|
|
38
|
+
"""Response with project configuration."""
|
|
39
|
+
|
|
40
|
+
scan_directories: list[str]
|
|
41
|
+
registered_count: int
|
|
42
|
+
favorites_count: int
|
|
18
43
|
|
|
19
44
|
|
|
20
45
|
class Memory(BaseModel):
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Project configuration manager for user preferences."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import platform
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RegisteredProject(BaseModel):
|
|
13
|
+
"""A manually registered project."""
|
|
14
|
+
|
|
15
|
+
path: str
|
|
16
|
+
display_name: Optional[str] = None
|
|
17
|
+
added_at: datetime
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RecentProject(BaseModel):
|
|
21
|
+
"""A recently accessed project."""
|
|
22
|
+
|
|
23
|
+
path: str
|
|
24
|
+
last_accessed: datetime
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProjectConfig(BaseModel):
|
|
28
|
+
"""User project configuration."""
|
|
29
|
+
|
|
30
|
+
version: int = 1
|
|
31
|
+
scan_directories: list[str] = []
|
|
32
|
+
registered_projects: list[RegisteredProject] = []
|
|
33
|
+
favorites: list[str] = []
|
|
34
|
+
recent: list[RecentProject] = []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
CONFIG_PATH = Path.home() / ".omni-cortex" / "projects.json"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_default_scan_dirs() -> list[str]:
|
|
41
|
+
"""Return platform-appropriate default scan directories."""
|
|
42
|
+
home = Path.home()
|
|
43
|
+
|
|
44
|
+
dirs = [
|
|
45
|
+
str(home / "projects"),
|
|
46
|
+
str(home / "Projects"),
|
|
47
|
+
str(home / "code"),
|
|
48
|
+
str(home / "Code"),
|
|
49
|
+
str(home / "dev"),
|
|
50
|
+
str(home / "workspace"),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
if platform.system() == "Windows":
|
|
54
|
+
dirs.insert(0, "D:/Projects")
|
|
55
|
+
|
|
56
|
+
return [d for d in dirs if Path(d).exists()]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_config() -> ProjectConfig:
|
|
60
|
+
"""Load config from disk, creating defaults if missing."""
|
|
61
|
+
if CONFIG_PATH.exists():
|
|
62
|
+
try:
|
|
63
|
+
data = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
|
64
|
+
return ProjectConfig(**data)
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
# Create default config
|
|
69
|
+
config = ProjectConfig(scan_directories=get_default_scan_dirs())
|
|
70
|
+
save_config(config)
|
|
71
|
+
return config
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def save_config(config: ProjectConfig) -> None:
|
|
75
|
+
"""Save config to disk."""
|
|
76
|
+
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
CONFIG_PATH.write_text(config.model_dump_json(indent=2), encoding="utf-8")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def add_registered_project(path: str, display_name: Optional[str] = None) -> bool:
|
|
81
|
+
"""Register a new project by path."""
|
|
82
|
+
config = load_config()
|
|
83
|
+
|
|
84
|
+
# Validate path has cortex.db
|
|
85
|
+
db_path = Path(path) / ".omni-cortex" / "cortex.db"
|
|
86
|
+
if not db_path.exists():
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# Check if already registered
|
|
90
|
+
if any(p.path == path for p in config.registered_projects):
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
config.registered_projects.append(
|
|
94
|
+
RegisteredProject(path=path, display_name=display_name, added_at=datetime.now())
|
|
95
|
+
)
|
|
96
|
+
save_config(config)
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def remove_registered_project(path: str) -> bool:
|
|
101
|
+
"""Remove a registered project."""
|
|
102
|
+
config = load_config()
|
|
103
|
+
original_len = len(config.registered_projects)
|
|
104
|
+
config.registered_projects = [
|
|
105
|
+
p for p in config.registered_projects if p.path != path
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
if len(config.registered_projects) < original_len:
|
|
109
|
+
save_config(config)
|
|
110
|
+
return True
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def toggle_favorite(path: str) -> bool:
|
|
115
|
+
"""Toggle favorite status for a project. Returns new favorite status."""
|
|
116
|
+
config = load_config()
|
|
117
|
+
|
|
118
|
+
if path in config.favorites:
|
|
119
|
+
config.favorites.remove(path)
|
|
120
|
+
is_favorite = False
|
|
121
|
+
else:
|
|
122
|
+
config.favorites.append(path)
|
|
123
|
+
is_favorite = True
|
|
124
|
+
|
|
125
|
+
save_config(config)
|
|
126
|
+
return is_favorite
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def update_recent(path: str) -> None:
|
|
130
|
+
"""Update recent projects list."""
|
|
131
|
+
config = load_config()
|
|
132
|
+
|
|
133
|
+
# Remove if already in list
|
|
134
|
+
config.recent = [r for r in config.recent if r.path != path]
|
|
135
|
+
|
|
136
|
+
# Add to front
|
|
137
|
+
config.recent.insert(0, RecentProject(path=path, last_accessed=datetime.now()))
|
|
138
|
+
|
|
139
|
+
# Keep only last 10
|
|
140
|
+
config.recent = config.recent[:10]
|
|
141
|
+
|
|
142
|
+
save_config(config)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def add_scan_directory(directory: str) -> bool:
|
|
146
|
+
"""Add a directory to scan list."""
|
|
147
|
+
config = load_config()
|
|
148
|
+
|
|
149
|
+
# Expand user path
|
|
150
|
+
expanded = str(Path(directory).expanduser())
|
|
151
|
+
|
|
152
|
+
if not Path(expanded).is_dir():
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
if expanded not in config.scan_directories:
|
|
156
|
+
config.scan_directories.append(expanded)
|
|
157
|
+
save_config(config)
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def remove_scan_directory(directory: str) -> bool:
|
|
163
|
+
"""Remove a directory from scan list."""
|
|
164
|
+
config = load_config()
|
|
165
|
+
|
|
166
|
+
if directory in config.scan_directories:
|
|
167
|
+
config.scan_directories.remove(directory)
|
|
168
|
+
save_config(config)
|
|
169
|
+
return True
|
|
170
|
+
return False
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Scanner to discover all omni-cortex databases on the system."""
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import sqlite3
|
|
5
4
|
from datetime import datetime
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
8
7
|
from models import ProjectInfo
|
|
8
|
+
from project_config import load_config
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def get_global_db_path() -> Path:
|
|
@@ -65,54 +65,70 @@ def scan_projects() -> list[ProjectInfo]:
|
|
|
65
65
|
projects: list[ProjectInfo] = []
|
|
66
66
|
seen_paths: set[str] = set()
|
|
67
67
|
|
|
68
|
+
# Load user config
|
|
69
|
+
config = load_config()
|
|
70
|
+
|
|
68
71
|
# 1. Add global index if exists
|
|
69
72
|
global_path = get_global_db_path()
|
|
70
73
|
if global_path.exists():
|
|
71
74
|
stat = global_path.stat()
|
|
75
|
+
global_project_path = str(global_path.parent)
|
|
72
76
|
projects.append(
|
|
73
77
|
ProjectInfo(
|
|
74
78
|
name="Global Index",
|
|
75
|
-
path=
|
|
79
|
+
path=global_project_path,
|
|
76
80
|
db_path=str(global_path),
|
|
77
81
|
last_modified=datetime.fromtimestamp(stat.st_mtime),
|
|
78
82
|
memory_count=get_memory_count(global_path),
|
|
79
83
|
is_global=True,
|
|
84
|
+
is_favorite=global_project_path in config.favorites,
|
|
80
85
|
)
|
|
81
86
|
)
|
|
82
87
|
seen_paths.add(str(global_path))
|
|
83
88
|
|
|
84
|
-
# 2.
|
|
85
|
-
|
|
86
|
-
Path(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Path.home() / "code",
|
|
90
|
-
Path.home() / "Code",
|
|
91
|
-
Path.home() / "dev",
|
|
92
|
-
Path.home() / "Dev",
|
|
93
|
-
Path.home() / "src",
|
|
94
|
-
Path.home() / "workspace",
|
|
95
|
-
]
|
|
96
|
-
|
|
97
|
-
for scan_dir in scan_dirs:
|
|
98
|
-
if scan_dir.exists():
|
|
99
|
-
for db_path in scan_directory_for_cortex(scan_dir):
|
|
89
|
+
# 2. Use CONFIGURABLE scan directories
|
|
90
|
+
for scan_dir in config.scan_directories:
|
|
91
|
+
scan_path = Path(scan_dir).expanduser()
|
|
92
|
+
if scan_path.exists():
|
|
93
|
+
for db_path in scan_directory_for_cortex(scan_path):
|
|
100
94
|
if str(db_path) not in seen_paths:
|
|
101
95
|
project_dir = db_path.parent.parent
|
|
102
96
|
stat = db_path.stat()
|
|
97
|
+
project_path = str(project_dir)
|
|
103
98
|
projects.append(
|
|
104
99
|
ProjectInfo(
|
|
105
100
|
name=project_dir.name,
|
|
106
|
-
path=
|
|
101
|
+
path=project_path,
|
|
107
102
|
db_path=str(db_path),
|
|
108
103
|
last_modified=datetime.fromtimestamp(stat.st_mtime),
|
|
109
104
|
memory_count=get_memory_count(db_path),
|
|
110
105
|
is_global=False,
|
|
106
|
+
is_favorite=project_path in config.favorites,
|
|
111
107
|
)
|
|
112
108
|
)
|
|
113
109
|
seen_paths.add(str(db_path))
|
|
114
110
|
|
|
115
|
-
# 3. Add
|
|
111
|
+
# 3. Add REGISTERED projects (manual additions)
|
|
112
|
+
for reg in config.registered_projects:
|
|
113
|
+
db_path = Path(reg.path) / ".omni-cortex" / "cortex.db"
|
|
114
|
+
if db_path.exists() and str(db_path) not in seen_paths:
|
|
115
|
+
stat = db_path.stat()
|
|
116
|
+
projects.append(
|
|
117
|
+
ProjectInfo(
|
|
118
|
+
name=Path(reg.path).name,
|
|
119
|
+
path=reg.path,
|
|
120
|
+
db_path=str(db_path),
|
|
121
|
+
last_modified=datetime.fromtimestamp(stat.st_mtime),
|
|
122
|
+
memory_count=get_memory_count(db_path),
|
|
123
|
+
is_global=False,
|
|
124
|
+
is_favorite=reg.path in config.favorites,
|
|
125
|
+
is_registered=True,
|
|
126
|
+
display_name=reg.display_name,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
seen_paths.add(str(db_path))
|
|
130
|
+
|
|
131
|
+
# 4. Add paths from global db that we haven't seen
|
|
116
132
|
for project_path in get_projects_from_global_db():
|
|
117
133
|
db_path = Path(project_path) / ".omni-cortex" / "cortex.db"
|
|
118
134
|
if db_path.exists() and str(db_path) not in seen_paths:
|
|
@@ -125,12 +141,19 @@ def scan_projects() -> list[ProjectInfo]:
|
|
|
125
141
|
last_modified=datetime.fromtimestamp(stat.st_mtime),
|
|
126
142
|
memory_count=get_memory_count(db_path),
|
|
127
143
|
is_global=False,
|
|
144
|
+
is_favorite=project_path in config.favorites,
|
|
128
145
|
)
|
|
129
146
|
)
|
|
130
147
|
seen_paths.add(str(db_path))
|
|
131
148
|
|
|
132
|
-
# Sort by last_modified (most recent first), with global always first
|
|
133
|
-
projects.sort(
|
|
149
|
+
# Sort: favorites first, then by last_modified (most recent first), with global always first
|
|
150
|
+
projects.sort(
|
|
151
|
+
key=lambda p: (
|
|
152
|
+
not p.is_global,
|
|
153
|
+
not p.is_favorite,
|
|
154
|
+
-(p.last_modified.timestamp() if p.last_modified else 0),
|
|
155
|
+
)
|
|
156
|
+
)
|
|
134
157
|
|
|
135
158
|
return projects
|
|
136
159
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|