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