abbacus-cortex 0.2.1__tar.gz → 0.2.2__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.
Files changed (66) hide show
  1. abbacus_cortex-0.2.2/LICENSE +69 -0
  2. abbacus_cortex-0.2.1/README.md → abbacus_cortex-0.2.2/PKG-INFO +66 -1
  3. abbacus_cortex-0.2.1/PKG-INFO → abbacus_cortex-0.2.2/README.md +26 -24
  4. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/pyproject.toml +28 -3
  5. abbacus_cortex-0.2.2/src/cortex/.DS_Store +0 -0
  6. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/__init__.py +1 -1
  7. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/cli/backup.py +9 -7
  8. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/cli/install.py +15 -11
  9. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/cli/main.py +25 -51
  10. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/core/constants.py +32 -26
  11. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/core/errors.py +5 -0
  12. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/core/logging.py +6 -4
  13. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/static/js/graph.js +26 -17
  14. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/db/content_store.py +31 -21
  15. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/db/graph_store.py +43 -37
  16. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/db/store.py +5 -4
  17. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/advanced_reason.py +17 -22
  18. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/enrich.py +1 -3
  19. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/importer.py +56 -32
  20. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/link.py +12 -10
  21. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/normalize.py +3 -1
  22. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/orchestrator.py +3 -1
  23. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/reason.py +11 -12
  24. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/templates.py +8 -3
  25. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/temporal.py +1 -3
  26. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/retrieval/engine.py +12 -27
  27. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/retrieval/graph.py +42 -39
  28. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/retrieval/learner.py +4 -9
  29. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/retrieval/presenters.py +66 -79
  30. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/services/embeddings.py +1 -3
  31. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/services/llm.py +86 -7
  32. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/transport/api/server.py +16 -46
  33. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/transport/mcp/client.py +6 -18
  34. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/transport/mcp/server.py +33 -21
  35. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/__main__.py +0 -0
  36. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/cli/__init__.py +0 -0
  37. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/core/__init__.py +0 -0
  38. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/core/config.py +0 -0
  39. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/__init__.py +0 -0
  40. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/server.py +0 -0
  41. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/static/css/style.css +0 -0
  42. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/base.html +0 -0
  43. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/create.html +0 -0
  44. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/detail.html +0 -0
  45. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/documents.html +0 -0
  46. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/entities.html +0 -0
  47. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/error.html +0 -0
  48. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/graph.html +0 -0
  49. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/home.html +0 -0
  50. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/login.html +0 -0
  51. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/settings.html +0 -0
  52. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/dashboard/templates/trail.html +0 -0
  53. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/db/__init__.py +0 -0
  54. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/ontology/__init__.py +0 -0
  55. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/ontology/cortex.ttl +0 -0
  56. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/ontology/namespaces.py +0 -0
  57. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/ontology/resolver.py +0 -0
  58. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/pipeline/__init__.py +0 -0
  59. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/py.typed +0 -0
  60. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/retrieval/__init__.py +0 -0
  61. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/services/__init__.py +0 -0
  62. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/tools/__init__.py +0 -0
  63. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/transport/__init__.py +0 -0
  64. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/transport/api/__init__.py +0 -0
  65. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/transport/mcp/__init__.py +0 -0
  66. {abbacus_cortex-0.2.1 → abbacus_cortex-0.2.2}/src/cortex/transport/mcp/__main__.py +0 -0
@@ -0,0 +1,69 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: Abbacus Group
6
+
7
+ Licensed Work: Cortex v0.2.1
8
+ The Licensed Work is the Cortex software, available at
9
+ https://github.com/abbacusgroup/Cortex
10
+
11
+ Additional Use Grant: You may use the Licensed Work for your internal
12
+ business purposes.
13
+
14
+ Change Date: 2030-04-11
15
+
16
+ Change License: MIT License
17
+
18
+ For information about alternative licensing arrangements for the Licensed Work,
19
+ please contact the Licensor.
20
+
21
+ Notice
22
+
23
+ Business Source License 1.1
24
+
25
+ License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
26
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
27
+
28
+ -----------------------------------------------------------------------------
29
+
30
+ Terms
31
+
32
+ The Licensor hereby grants you the right to copy, modify, create derivative
33
+ works, redistribute, and make non-production use of the Licensed Work. The
34
+ Licensor may make an Additional Use Grant, above, permitting limited production
35
+ use.
36
+
37
+ Effective on the Change Date, or the fourth anniversary of the first publicly
38
+ available distribution of a specific version of the Licensed Work under this
39
+ License, whichever comes first, the Licensor hereby grants you rights under the
40
+ terms of the Change License, and the rights granted in the paragraph above
41
+ terminate.
42
+
43
+ If your use of the Licensed Work does not comply with the requirements currently
44
+ in effect as described in this License, you must purchase a commercial license
45
+ from the Licensor, its affiliated entities, or authorized resellers, or you must
46
+ refrain from using the Licensed Work.
47
+
48
+ All copies of the original and modified Licensed Work, and derivative works of
49
+ the Licensed Work, are subject to this License. This License applies separately
50
+ for each version of the Licensed Work and the Change Date may vary for each
51
+ version of the Licensed Work released by Licensor.
52
+
53
+ You must conspicuously display this License on each original or modified copy of
54
+ the Licensed Work. If you receive the Licensed Work in original or modified form
55
+ from a third party, the terms and conditions set forth in this License apply to
56
+ your use of that work.
57
+
58
+ Any use of the Licensed Work in violation of this License will automatically
59
+ terminate your rights under this License for the current and all other versions
60
+ of the Licensed Work.
61
+
62
+ This License does not grant you any right in any trademark or logo of Licensor
63
+ or its affiliates (provided that you may use a trademark or logo of Licensor as
64
+ expressly required by this License).
65
+
66
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
67
+ "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS
68
+ OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY,
69
+ FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
@@ -1,9 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: abbacus-cortex
3
+ Version: 0.2.2
4
+ Summary: Cognitive knowledge system with formal ontology, reasoning, and intelligence serving
5
+ Keywords: knowledge-graph,knowledge-management,mcp,reasoning,ontology,rdf,ai,semantic-search,llm
6
+ Author: Fabrizzio Silveira
7
+ Author-email: Fabrizzio Silveira <74255714+grayisnotacolor@users.noreply.github.com>
8
+ License-Expression: BUSL-1.1
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: Other/Proprietary License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Database :: Database Engines/Servers
19
+ Classifier: Typing :: Typed
20
+ Requires-Dist: pyoxigraph>=0.4
21
+ Requires-Dist: aiosqlite>=0.20
22
+ Requires-Dist: fastapi>=0.115
23
+ Requires-Dist: uvicorn[standard]>=0.34
24
+ Requires-Dist: typer>=0.15
25
+ Requires-Dist: jinja2>=3.1
26
+ Requires-Dist: python-dotenv>=1.1
27
+ Requires-Dist: bcrypt>=4.2
28
+ Requires-Dist: mcp>=1.6
29
+ Requires-Dist: httpx>=0.28
30
+ Requires-Dist: litellm>=1.60
31
+ Requires-Dist: sentence-transformers>=3.4 ; extra == 'embeddings'
32
+ Requires-Python: >=3.12
33
+ Project-URL: Homepage, https://github.com/abbacusgroup/Cortex
34
+ Project-URL: Repository, https://github.com/abbacusgroup/Cortex
35
+ Project-URL: Documentation, https://github.com/abbacusgroup/Cortex#readme
36
+ Project-URL: Changelog, https://github.com/abbacusgroup/Cortex/blob/main/CHANGELOG.md
37
+ Project-URL: Bug Tracker, https://github.com/abbacusgroup/Cortex/issues
38
+ Provides-Extra: embeddings
39
+ Description-Content-Type: text/markdown
40
+
1
41
  # Cortex
2
42
 
43
+ <!-- mcp-name: io.github.abbacusgroup/cortex -->
44
+
45
+ [![CI](https://github.com/abbacusgroup/Cortex/actions/workflows/test.yml/badge.svg)](https://github.com/abbacusgroup/Cortex/actions/workflows/test.yml)
46
+ [![PyPI](https://img.shields.io/pypi/v/abbacus-cortex)](https://pypi.org/project/abbacus-cortex/)
47
+ [![Python](https://img.shields.io/pypi/pyversions/abbacus-cortex)](https://pypi.org/project/abbacus-cortex/)
48
+ [![License](https://img.shields.io/badge/license-BUSL--1.1-blue)](LICENSE)
49
+
3
50
  Cognitive knowledge system with formal ontology, reasoning, and intelligence serving.
4
51
 
5
52
  Cortex captures knowledge objects (decisions, lessons, fixes, sessions, research, ideas), classifies them with an OWL-RL ontology, discovers relationships, reasons over the graph, and serves intelligence through hybrid retrieval.
6
53
 
54
+ ![Dashboard](docs/dashboard/dashboard4.png)
55
+
56
+ ![Knowledge Graph](docs/dashboard/dashboard2.png)
57
+
7
58
  ## Install
8
59
 
9
60
  ```bash
@@ -187,6 +238,20 @@ decision, lesson, fix, session, research, source, synthesis, idea
187
238
 
188
239
  causedBy, contradicts (symmetric), supports, supersedes (transitive), dependsOn, ledTo (inverse of causedBy), implements, mentions
189
240
 
241
+ ## Privacy
242
+
243
+ Cortex stores all data locally. No telemetry, no analytics, no phone-home. If you configure an LLM provider (via `CORTEX_LLM_API_KEY`), object content may be sent to that provider for classification and reasoning. Embeddings are computed locally by default using `sentence-transformers`.
244
+
190
245
  ## License
191
246
 
192
- Copyright Abbacus Group.
247
+ Copyright (c) 2026 Abbacus Group. Licensed under the [Business Source License 1.1](LICENSE).
248
+
249
+ - **Additional Use Grant:** You may use the Licensed Work for your internal business purposes.
250
+ - **Change Date:** 2030-04-11
251
+ - **Change License:** MIT
252
+
253
+ After the Change Date, this software converts to the MIT license.
254
+
255
+ ## Trademark Notice
256
+
257
+ Cortex is a project of Abbacus Group and is not affiliated with any other product named Cortex.
@@ -1,32 +1,20 @@
1
- Metadata-Version: 2.3
2
- Name: abbacus-cortex
3
- Version: 0.2.1
4
- Summary: Cognitive knowledge system with formal ontology, reasoning, and intelligence serving
5
- Author: Fabrizzio Silveira
6
- Author-email: Fabrizzio Silveira <74255714+grayisnotacolor@users.noreply.github.com>
7
- Requires-Dist: pyoxigraph>=0.4
8
- Requires-Dist: aiosqlite>=0.20
9
- Requires-Dist: fastapi>=0.115
10
- Requires-Dist: uvicorn[standard]>=0.34
11
- Requires-Dist: typer>=0.15
12
- Requires-Dist: jinja2>=3.1
13
- Requires-Dist: python-dotenv>=1.1
14
- Requires-Dist: bcrypt>=4.2
15
- Requires-Dist: mcp>=1.6
16
- Requires-Dist: httpx>=0.28
17
- Requires-Dist: sentence-transformers>=3.4 ; extra == 'embeddings'
18
- Requires-Dist: litellm>=1.60 ; extra == 'llm'
19
- Requires-Python: >=3.12
20
- Provides-Extra: embeddings
21
- Provides-Extra: llm
22
- Description-Content-Type: text/markdown
23
-
24
1
  # Cortex
25
2
 
3
+ <!-- mcp-name: io.github.abbacusgroup/cortex -->
4
+
5
+ [![CI](https://github.com/abbacusgroup/Cortex/actions/workflows/test.yml/badge.svg)](https://github.com/abbacusgroup/Cortex/actions/workflows/test.yml)
6
+ [![PyPI](https://img.shields.io/pypi/v/abbacus-cortex)](https://pypi.org/project/abbacus-cortex/)
7
+ [![Python](https://img.shields.io/pypi/pyversions/abbacus-cortex)](https://pypi.org/project/abbacus-cortex/)
8
+ [![License](https://img.shields.io/badge/license-BUSL--1.1-blue)](LICENSE)
9
+
26
10
  Cognitive knowledge system with formal ontology, reasoning, and intelligence serving.
27
11
 
28
12
  Cortex captures knowledge objects (decisions, lessons, fixes, sessions, research, ideas), classifies them with an OWL-RL ontology, discovers relationships, reasons over the graph, and serves intelligence through hybrid retrieval.
29
13
 
14
+ ![Dashboard](docs/dashboard/dashboard4.png)
15
+
16
+ ![Knowledge Graph](docs/dashboard/dashboard2.png)
17
+
30
18
  ## Install
31
19
 
32
20
  ```bash
@@ -210,6 +198,20 @@ decision, lesson, fix, session, research, source, synthesis, idea
210
198
 
211
199
  causedBy, contradicts (symmetric), supports, supersedes (transitive), dependsOn, ledTo (inverse of causedBy), implements, mentions
212
200
 
201
+ ## Privacy
202
+
203
+ Cortex stores all data locally. No telemetry, no analytics, no phone-home. If you configure an LLM provider (via `CORTEX_LLM_API_KEY`), object content may be sent to that provider for classification and reasoning. Embeddings are computed locally by default using `sentence-transformers`.
204
+
213
205
  ## License
214
206
 
215
- Copyright Abbacus Group.
207
+ Copyright (c) 2026 Abbacus Group. Licensed under the [Business Source License 1.1](LICENSE).
208
+
209
+ - **Additional Use Grant:** You may use the Licensed Work for your internal business purposes.
210
+ - **Change Date:** 2030-04-11
211
+ - **Change License:** MIT
212
+
213
+ After the Change Date, this software converts to the MIT license.
214
+
215
+ ## Trademark Notice
216
+
217
+ Cortex is a project of Abbacus Group and is not affiliated with any other product named Cortex.
@@ -1,12 +1,28 @@
1
1
  [project]
2
2
  name = "abbacus-cortex"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Cognitive knowledge system with formal ontology, reasoning, and intelligence serving"
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Fabrizzio Silveira", email = "74255714+grayisnotacolor@users.noreply.github.com" }
8
8
  ]
9
+ license = "BUSL-1.1"
10
+ license-files = ["LICENSE"]
9
11
  requires-python = ">=3.12"
12
+ keywords = ["knowledge-graph", "knowledge-management", "mcp", "reasoning", "ontology", "rdf", "ai", "semantic-search", "llm"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "License :: Other/Proprietary License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
22
+ "Topic :: Database :: Database Engines/Servers",
23
+ "Typing :: Typed",
24
+ ]
25
+
10
26
  dependencies = [
11
27
  # Graph / Ontology
12
28
  "pyoxigraph>=0.4",
@@ -27,11 +43,19 @@ dependencies = [
27
43
  "mcp>=1.6",
28
44
  # HTTP client
29
45
  "httpx>=0.28",
46
+ # LLM (provider-agnostic: Anthropic, OpenAI, Ollama, etc.)
47
+ "litellm>=1.60",
30
48
  ]
31
49
 
50
+ [project.urls]
51
+ Homepage = "https://github.com/abbacusgroup/Cortex"
52
+ Repository = "https://github.com/abbacusgroup/Cortex"
53
+ Documentation = "https://github.com/abbacusgroup/Cortex#readme"
54
+ Changelog = "https://github.com/abbacusgroup/Cortex/blob/main/CHANGELOG.md"
55
+ "Bug Tracker" = "https://github.com/abbacusgroup/Cortex/issues"
56
+
32
57
  [project.optional-dependencies]
33
58
  embeddings = ["sentence-transformers>=3.4"]
34
- llm = ["litellm>=1.60"]
35
59
 
36
60
  [project.scripts]
37
61
  cortex = "cortex.cli.main:app"
@@ -57,11 +81,12 @@ build-backend = "uv_build"
57
81
  build-backend.module-name = "cortex"
58
82
 
59
83
  [tool.pytest.ini_options]
60
- testpaths = ["tests"]
84
+ testpaths = ["tests", "benchmarks"]
61
85
  asyncio_mode = "auto"
62
86
  pythonpath = ["src"]
63
87
  markers = [
64
88
  "slow: end-to-end tests that spawn subprocesses (skipped in fast loops)",
89
+ "bench: benchmark suite (run with -m bench)",
65
90
  ]
66
91
 
67
92
  [tool.ruff]
@@ -1,3 +1,3 @@
1
1
  """Cortex — Cognitive knowledge system."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
@@ -21,13 +21,15 @@ from cortex.core.config import CortexConfig
21
21
  # ---------------------------------------------------------------------------
22
22
 
23
23
  # Relative paths (from data_dir) to exclude from backup archives.
24
- _EXCLUDE_EXACT = frozenset({
25
- "graph.db.lock",
26
- "graph.db/LOCK",
27
- ".env",
28
- "cortex.db-wal",
29
- "cortex.db-shm",
30
- })
24
+ _EXCLUDE_EXACT = frozenset(
25
+ {
26
+ "graph.db.lock",
27
+ "graph.db/LOCK",
28
+ ".env",
29
+ "cortex.db-wal",
30
+ "cortex.db-shm",
31
+ }
32
+ )
31
33
 
32
34
  _EXCLUDE_SUFFIXES = (".log", ".err", ".log.old", ".err.old")
33
35
 
@@ -294,6 +294,13 @@ def _unit_name(label: str) -> str:
294
294
 
295
295
  def _install_systemd_unit(label: str, content: str) -> Path:
296
296
  """Write a unit file and enable it."""
297
+ if not shutil.which("systemctl"):
298
+ typer.echo(
299
+ " Error: systemctl not found — systemd is required for service install on Linux"
300
+ )
301
+ typer.echo(" You can still run Cortex manually: cortex serve --transport mcp-http")
302
+ raise typer.Exit(1)
303
+
297
304
  _SYSTEMD_USER_DIR.mkdir(parents=True, exist_ok=True)
298
305
  unit_path = _SYSTEMD_USER_DIR / _unit_name(label)
299
306
  unit_name = unit_path.name
@@ -322,11 +329,12 @@ def _uninstall_systemd_unit(label: str) -> None:
322
329
  if not unit_path.exists():
323
330
  typer.echo(f" {unit_name}: not installed")
324
331
  return
325
- subprocess.run(
326
- ["systemctl", "--user", "disable", "--now", unit_name],
327
- check=False,
328
- capture_output=True,
329
- )
332
+ if shutil.which("systemctl"):
333
+ subprocess.run(
334
+ ["systemctl", "--user", "disable", "--now", unit_name],
335
+ check=False,
336
+ capture_output=True,
337
+ )
330
338
  unit_path.unlink()
331
339
  subprocess.run(
332
340
  ["systemctl", "--user", "daemon-reload"],
@@ -357,16 +365,12 @@ def do_install(config: CortexConfig, service: str) -> None:
357
365
  if svc == "mcp":
358
366
  _install_launchagent(_MCP_LABEL, render_mcp_plist(config, binary))
359
367
  else:
360
- _install_launchagent(
361
- _DASHBOARD_LABEL, render_dashboard_plist(config, binary)
362
- )
368
+ _install_launchagent(_DASHBOARD_LABEL, render_dashboard_plist(config, binary))
363
369
  else:
364
370
  if svc == "mcp":
365
371
  _install_systemd_unit(_MCP_LABEL, render_mcp_unit(config, binary))
366
372
  else:
367
- _install_systemd_unit(
368
- _DASHBOARD_LABEL, render_dashboard_unit(config, binary)
369
- )
373
+ _install_systemd_unit(_DASHBOARD_LABEL, render_dashboard_unit(config, binary))
370
374
 
371
375
  typer.echo()
372
376
  if "mcp" in services:
@@ -574,7 +574,9 @@ def status() -> None:
574
574
  store = _get_store(must_init=False)
575
575
  stats = store.status()
576
576
 
577
- typer.echo("Cortex v0.1.0")
577
+ from cortex import __version__
578
+
579
+ typer.echo(f"Cortex v{__version__}")
578
580
  typer.echo(f" Initialized: {stats['initialized']}")
579
581
  typer.echo(f" Documents: {stats['sqlite_total']}")
580
582
  typer.echo(f" Triples: {stats['graph_triples']}")
@@ -594,9 +596,7 @@ def context(
594
596
  ) -> None:
595
597
  """Get a briefing (summaries only) for a topic."""
596
598
  if _use_mcp():
597
- briefs = _mcp_call_or_exit(
598
- lambda: _get_mcp_client().context(topic=topic, limit=limit)
599
- )
599
+ briefs = _mcp_call_or_exit(lambda: _get_mcp_client().context(topic=topic, limit=limit))
600
600
  else:
601
601
  store = _get_store()
602
602
  from cortex.retrieval.engine import RetrievalEngine
@@ -624,9 +624,7 @@ def dossier(
624
624
  ) -> None:
625
625
  """Build an intelligence dossier around an entity or topic."""
626
626
  if _use_mcp():
627
- result = _mcp_call_or_exit(
628
- lambda: _get_mcp_client().dossier(topic=topic)
629
- )
627
+ result = _mcp_call_or_exit(lambda: _get_mcp_client().dossier(topic=topic))
630
628
  else:
631
629
  store = _get_store()
632
630
  from cortex.retrieval.presenters import DossierPresenter
@@ -671,9 +669,7 @@ def graph(
671
669
  ) -> None:
672
670
  """Show an object's relationships and graph neighborhood."""
673
671
  if _use_mcp():
674
- result = _mcp_call_or_exit(
675
- lambda: _get_mcp_client().graph(obj_id=obj_id)
676
- )
672
+ result = _mcp_call_or_exit(lambda: _get_mcp_client().graph(obj_id=obj_id))
677
673
  chain = result.get("causal_chain", [])
678
674
  timeline = result.get("evolution", [])
679
675
  rels = result.get("relationships", [])
@@ -719,9 +715,7 @@ def synthesize(
719
715
  """Generate a synthesis of recent knowledge."""
720
716
  if _use_mcp():
721
717
  result = _mcp_call_or_exit(
722
- lambda: _get_mcp_client().synthesize(
723
- period_days=period, project=project or ""
724
- )
718
+ lambda: _get_mcp_client().synthesize(period_days=period, project=project or "")
725
719
  )
726
720
  else:
727
721
  store = _get_store()
@@ -966,6 +960,7 @@ def serve(
966
960
  """
967
961
  if transport == "stdio":
968
962
  from cortex.transport.mcp.server import run_stdio
963
+
969
964
  try:
970
965
  run_stdio()
971
966
  except StoreLockedError as e:
@@ -973,6 +968,7 @@ def serve(
973
968
  raise typer.Exit(1) from e
974
969
  elif transport == "mcp-http":
975
970
  from cortex.transport.mcp.server import run_http
971
+
976
972
  typer.echo(f"Cortex MCP (streamable-http) at http://{host}:{port}/mcp")
977
973
  if parent_watchdog:
978
974
  _start_parent_watchdog()
@@ -1096,6 +1092,7 @@ def setup(
1096
1092
  typer.echo(f" LLM: {config.llm_model}")
1097
1093
  try:
1098
1094
  from cortex.services.llm import LLMClient
1095
+
1099
1096
  llm = LLMClient(config)
1100
1097
  llm.complete("Say 'connected' in one word.")
1101
1098
  typer.echo(" LLM: Connected")
@@ -1378,15 +1375,12 @@ def dashboard(
1378
1375
 
1379
1376
  # --spawn-mcp: launch the MCP server as a subprocess and wait.
1380
1377
  typer.secho(
1381
- f"MCP server at {config.mcp_server_url} unreachable — "
1382
- f"spawning one via --spawn-mcp…",
1378
+ f"MCP server at {config.mcp_server_url} unreachable — spawning one via --spawn-mcp…",
1383
1379
  fg=typer.colors.YELLOW,
1384
1380
  err=True,
1385
1381
  )
1386
1382
  try:
1387
- spawned_proc = _spawn_mcp_subprocess(
1388
- config.mcp_server_url, config.data_dir
1389
- )
1383
+ spawned_proc = _spawn_mcp_subprocess(config.mcp_server_url, config.data_dir)
1390
1384
  except RuntimeError as spawn_err:
1391
1385
  typer.secho(
1392
1386
  f"Failed to spawn MCP subprocess: {spawn_err}",
@@ -1506,9 +1500,7 @@ def run_pipeline_cmd(
1506
1500
  raise typer.Exit(1)
1507
1501
 
1508
1502
  if _use_mcp():
1509
- result = _mcp_call_or_exit(
1510
- lambda: _get_mcp_client().pipeline(obj_id=obj_id)
1511
- )
1503
+ result = _mcp_call_or_exit(lambda: _get_mcp_client().pipeline(obj_id=obj_id))
1512
1504
  if "error" in result:
1513
1505
  typer.echo(result["error"], err=True)
1514
1506
  raise typer.Exit(1)
@@ -1635,15 +1627,10 @@ def doctor_unlock(
1635
1627
  if dry_run:
1636
1628
  typer.echo("Dry run — no files will be removed.")
1637
1629
  typer.echo(f" marker: {marker_path} (exists={marker_path.exists()})")
1638
- typer.echo(
1639
- f" rocksdb LOCK: {rocksdb_lock} (exists={rocksdb_lock.exists()})"
1640
- )
1630
+ typer.echo(f" rocksdb LOCK: {rocksdb_lock} (exists={rocksdb_lock.exists()})")
1641
1631
  if holder_pid is not None:
1642
1632
  alive = _pid_alive(holder_pid)
1643
- typer.echo(
1644
- f" holder PID: {holder_pid} "
1645
- f"({'alive' if alive else 'dead'})"
1646
- )
1633
+ typer.echo(f" holder PID: {holder_pid} ({'alive' if alive else 'dead'})")
1647
1634
  if holder_cmdline:
1648
1635
  typer.echo(f" holder cmdline: {holder_cmdline}")
1649
1636
  raise typer.Exit(0)
@@ -1689,8 +1676,7 @@ def doctor_unlock(
1689
1676
  rocksdb_lock.unlink(missing_ok=True)
1690
1677
  removed.append(str(rocksdb_lock))
1691
1678
  typer.secho(
1692
- f"Unlocked. No holder PID known; removed: "
1693
- f"{', '.join(removed) or 'nothing'}",
1679
+ f"Unlocked. No holder PID known; removed: {', '.join(removed) or 'nothing'}",
1694
1680
  fg=typer.colors.GREEN,
1695
1681
  )
1696
1682
  raise typer.Exit(0)
@@ -1703,9 +1689,7 @@ def doctor_unlock(
1703
1689
  and holder_cmdline is not None
1704
1690
  and live_cmdline != holder_cmdline
1705
1691
  )
1706
- cmdline_unknown = (
1707
- live_cmdline is None and holder_cmdline is not None
1708
- )
1692
+ cmdline_unknown = live_cmdline is None and holder_cmdline is not None
1709
1693
  if is_reuse:
1710
1694
  typer.secho(
1711
1695
  f"PID {holder_pid} is alive but its cmdline does NOT match "
@@ -1727,14 +1711,12 @@ def doctor_unlock(
1727
1711
  err=True,
1728
1712
  )
1729
1713
  typer.echo(
1730
- " If you are sure the marker is stale, run: "
1731
- "cortex doctor unlock --force",
1714
+ " If you are sure the marker is stale, run: cortex doctor unlock --force",
1732
1715
  err=True,
1733
1716
  )
1734
1717
  else:
1735
1718
  typer.secho(
1736
- f"PID {holder_pid} is still running — refusing to unlock "
1737
- f"a live holder.",
1719
+ f"PID {holder_pid} is still running — refusing to unlock a live holder.",
1738
1720
  fg=typer.colors.RED,
1739
1721
  err=True,
1740
1722
  )
@@ -1757,8 +1739,7 @@ def doctor_unlock(
1757
1739
  raise typer.Exit(1)
1758
1740
 
1759
1741
  typer.secho(
1760
- f"Unlocked. Holder PID {holder_pid} was dead; removed "
1761
- f"{marker_path} and {rocksdb_lock}.",
1742
+ f"Unlocked. Holder PID {holder_pid} was dead; removed {marker_path} and {rocksdb_lock}.",
1762
1743
  fg=typer.colors.GREEN,
1763
1744
  )
1764
1745
 
@@ -1777,7 +1758,7 @@ _LAUNCHAGENT_LOG_FILENAMES = (
1777
1758
  )
1778
1759
 
1779
1760
  # Size thresholds for the summary status badge.
1780
- _LOG_SIZE_GREEN_MAX = 10 * 1024 * 1024 # 10 MB
1761
+ _LOG_SIZE_GREEN_MAX = 10 * 1024 * 1024 # 10 MB
1781
1762
  _LOG_SIZE_YELLOW_MAX = 100 * 1024 * 1024 # 100 MB
1782
1763
 
1783
1764
 
@@ -1840,9 +1821,7 @@ def _summarize_logs(log_paths: list[Path]) -> None:
1840
1821
  stat = path.stat()
1841
1822
  size_bytes = stat.st_size
1842
1823
  lines = _count_lines(path)
1843
- mtime = datetime.datetime.fromtimestamp(stat.st_mtime).strftime(
1844
- "%Y-%m-%d %H:%M:%S"
1845
- )
1824
+ mtime = datetime.datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
1846
1825
  color = _log_status_color(size_bytes)
1847
1826
  label = _log_status_label(size_bytes)
1848
1827
  typer.echo(
@@ -2015,9 +1994,7 @@ def doctor_check() -> None:
2015
1994
  try:
2016
1995
  fts = content.fts_integrity_check()
2017
1996
  if fts["ok"]:
2018
- typer.echo(
2019
- f" FTS5 index: OK ({fts['documents_count']} documents indexed)"
2020
- )
1997
+ typer.echo(f" FTS5 index: OK ({fts['documents_count']} documents indexed)")
2021
1998
  else:
2022
1999
  all_ok = False
2023
2000
  typer.secho(
@@ -2042,8 +2019,7 @@ def doctor_check() -> None:
2042
2019
  else:
2043
2020
  all_ok = False
2044
2021
  typer.secho(
2045
- f" Reasoner: WARN — {fixpoint['total_pending']} "
2046
- "triples pending inference",
2022
+ f" Reasoner: WARN — {fixpoint['total_pending']} triples pending inference",
2047
2023
  fg=typer.colors.YELLOW,
2048
2024
  )
2049
2025
  typer.echo(" Run `cortex doctor repair` to reach fixpoint.")
@@ -2113,9 +2089,7 @@ def doctor_repair() -> None:
2113
2089
  content = ContentStore(path=config.sqlite_db_path)
2114
2090
  try:
2115
2091
  result = content.fts_rebuild()
2116
- typer.echo(
2117
- f" FTS5 index: rebuilt ({result['documents_count']} documents reindexed)"
2118
- )
2092
+ typer.echo(f" FTS5 index: rebuilt ({result['documents_count']} documents reindexed)")
2119
2093
  finally:
2120
2094
  content.close()
2121
2095
 
@@ -19,36 +19,42 @@ DEFAULT_HOST = "127.0.0.1"
19
19
  DEFAULT_PORT = 1314
20
20
 
21
21
  # Knowledge object types (must match ontology classes)
22
- KNOWLEDGE_TYPES = frozenset({
23
- "decision",
24
- "lesson",
25
- "fix",
26
- "session",
27
- "research",
28
- "source",
29
- "synthesis",
30
- "idea",
31
- })
22
+ KNOWLEDGE_TYPES = frozenset(
23
+ {
24
+ "decision",
25
+ "lesson",
26
+ "fix",
27
+ "session",
28
+ "research",
29
+ "source",
30
+ "synthesis",
31
+ "idea",
32
+ }
33
+ )
32
34
 
33
35
  # Relationship types (must match ontology object properties)
34
- RELATIONSHIP_TYPES = frozenset({
35
- "causedBy",
36
- "contradicts",
37
- "supports",
38
- "supersedes",
39
- "dependsOn",
40
- "ledTo",
41
- "implements",
42
- "mentions",
43
- })
36
+ RELATIONSHIP_TYPES = frozenset(
37
+ {
38
+ "causedBy",
39
+ "contradicts",
40
+ "supports",
41
+ "supersedes",
42
+ "dependsOn",
43
+ "ledTo",
44
+ "implements",
45
+ "mentions",
46
+ }
47
+ )
44
48
 
45
49
  # Entity subtypes
46
- ENTITY_TYPES = frozenset({
47
- "technology",
48
- "project",
49
- "pattern",
50
- "concept",
51
- })
50
+ ENTITY_TYPES = frozenset(
51
+ {
52
+ "technology",
53
+ "project",
54
+ "pattern",
55
+ "concept",
56
+ }
57
+ )
52
58
 
53
59
  # Tiers
54
60
  TIERS = frozenset({"archive", "recall", "reflex"})