cortex-kg 0.2.0__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 (111) hide show
  1. {cortex_kg-0.2.0/src/cortex_kg.egg-info → cortex_kg-0.2.2}/PKG-INFO +1 -1
  2. cortex_kg-0.2.2/docs/cortex-brain.md +186 -0
  3. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/pyproject.toml +1 -1
  4. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/__init__.py +1 -1
  5. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/api/rest.py +35 -3
  6. cortex_kg-0.2.2/src/cortex/api/workspace_viz.py +579 -0
  7. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/daemon.py +34 -1
  8. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/models.py +14 -0
  9. cortex_kg-0.2.2/src/cortex/static/brain/assets/index-BUW5gdiE.js +486 -0
  10. cortex_kg-0.2.2/src/cortex/static/brain/assets/index-C5hkz5MJ.css +1 -0
  11. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/static/brain/index.html +2 -2
  12. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/storage/graph_store.py +15 -1
  13. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/workspace/registry.py +25 -0
  14. {cortex_kg-0.2.0 → cortex_kg-0.2.2/src/cortex_kg.egg-info}/PKG-INFO +1 -1
  15. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex_kg.egg-info/SOURCES.txt +3 -2
  16. cortex_kg-0.2.0/docs/cortex-brain.md +0 -90
  17. cortex_kg-0.2.0/src/cortex/static/brain/assets/index-B8habZrD.js +0 -486
  18. cortex_kg-0.2.0/src/cortex/static/brain/assets/index-Dvrg6tJc.css +0 -1
  19. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/LICENSE +0 -0
  20. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/MANIFEST.in +0 -0
  21. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/README.md +0 -0
  22. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/README.md +0 -0
  23. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/automation.md +0 -0
  24. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/cursor.md +0 -0
  25. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/installation.md +0 -0
  26. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/other-platforms.md +0 -0
  27. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/project-workflow.md +0 -0
  28. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/publishing.md +0 -0
  29. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/templates/cortex-agent-rule.mdc +0 -0
  30. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/docs/templates/cortex-mcp.json +0 -0
  31. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/examples/sample_repo/api/openapi.yaml +0 -0
  32. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/examples/sample_repo/docs/adr-001-session-tokens.md +0 -0
  33. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/examples/sample_repo/docs/architecture.md +0 -0
  34. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/examples/sample_repo/notes/meeting-2026-07-10-billing-sync.md +0 -0
  35. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/examples/sample_repo/schema/schema.sql +0 -0
  36. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/examples/sample_repo/src/auth_service.py +0 -0
  37. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/examples/sample_repo/src/billing_service.py +0 -0
  38. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/scripts/build_brain_ui.sh +0 -0
  39. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/sdk/python/cortex_sdk/__init__.py +0 -0
  40. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/sdk/python/pyproject.toml +0 -0
  41. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/sdk/typescript/package.json +0 -0
  42. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/sdk/typescript/src/index.ts +0 -0
  43. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/sdk/typescript/tsconfig.json +0 -0
  44. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/setup.cfg +0 -0
  45. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/agents/__init__.py +0 -0
  46. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/agents/hooks.py +0 -0
  47. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/agents/writeback.py +0 -0
  48. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/api/__init__.py +0 -0
  49. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/api/graph_viz.py +0 -0
  50. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/cli.py +0 -0
  51. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/config.py +0 -0
  52. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/context/__init__.py +0 -0
  53. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/context/generator.py +0 -0
  54. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/__init__.py +0 -0
  55. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/api_spec_extractor.py +0 -0
  56. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/base.py +0 -0
  57. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/code_extractor.py +0 -0
  58. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/db_schema_extractor.py +0 -0
  59. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/git_extractor.py +0 -0
  60. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/image_extractor.py +0 -0
  61. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/llm_relation_extractor.py +0 -0
  62. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/markdown_extractor.py +0 -0
  63. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/notes_extractor.py +0 -0
  64. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/extractors/pdf_extractor.py +0 -0
  65. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/ingestion/__init__.py +0 -0
  66. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/ingestion/content_hash.py +0 -0
  67. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/ingestion/pipeline.py +0 -0
  68. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/ingestion/resolver.py +0 -0
  69. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/mcp_server/__init__.py +0 -0
  70. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/mcp_server/server.py +0 -0
  71. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/offline/__init__.py +0 -0
  72. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/offline/local_llm.py +0 -0
  73. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/ontology/__init__.py +0 -0
  74. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/ontology/registry.py +0 -0
  75. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/plugins/__init__.py +0 -0
  76. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/plugins/examples/jira_like_plugin/plugin.py +0 -0
  77. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/plugins/interface.py +0 -0
  78. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/plugins/loader.py +0 -0
  79. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/replication/__init__.py +0 -0
  80. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/replication/crdt.py +0 -0
  81. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/retrieval/__init__.py +0 -0
  82. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/retrieval/embeddings.py +0 -0
  83. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/retrieval/fusion.py +0 -0
  84. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/retrieval/planner.py +0 -0
  85. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/retrieval/query_engine.py +0 -0
  86. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/retrieval/traversal.py +0 -0
  87. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/security/__init__.py +0 -0
  88. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/security/acl.py +0 -0
  89. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/security/audit.py +0 -0
  90. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/security/encryption.py +0 -0
  91. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/security/secrets_scanner.py +0 -0
  92. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/server_profile/__init__.py +0 -0
  93. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/server_profile/neo4j_store.py +0 -0
  94. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/setup/__init__.py +0 -0
  95. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/setup/generators.py +0 -0
  96. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/storage/__init__.py +0 -0
  97. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/storage/keyword_store.py +0 -0
  98. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/storage/provenance.py +0 -0
  99. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/storage/vector_store.py +0 -0
  100. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/sync/__init__.py +0 -0
  101. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/sync/change_feed.py +0 -0
  102. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/sync/job_queue.py +0 -0
  103. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/sync/watcher.py +0 -0
  104. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/versioning/__init__.py +0 -0
  105. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/versioning/bitemporal.py +0 -0
  106. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/workspace/__init__.py +0 -0
  107. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex/workspace/federation.py +0 -0
  108. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex_kg.egg-info/dependency_links.txt +0 -0
  109. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex_kg.egg-info/entry_points.txt +0 -0
  110. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex_kg.egg-info/requires.txt +0 -0
  111. {cortex_kg-0.2.0 → cortex_kg-0.2.2}/src/cortex_kg.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cortex-kg
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Cortex: a persistent, workspace-level Engineering Knowledge Graph.
5
5
  Author-email: Roshan Gamage <roshangamage01@users.noreply.github.com>
6
6
  License: MIT
@@ -0,0 +1,186 @@
1
+ # Cortex Brain — interactive knowledge graph UI
2
+
3
+ Cortex Brain is the WebGL visualization of your Engineering Knowledge Graph.
4
+ After install, you open it in a browser — **no separate frontend server**.
5
+
6
+ ## One-time setup (3 commands)
7
+
8
+ ```bash
9
+ # 1. Install
10
+ pipx install cortex-kg
11
+ # or: pip install cortex-kg
12
+
13
+ # 2. Index a project (creates knowledge in a workspace)
14
+ cortex --workspace default ingest /path/to/your/repo --project MyApp
15
+
16
+ # 3. Start the API + UI
17
+ cortex --workspace default serve-api
18
+ ```
19
+
20
+ Open **http://localhost:8420** in your browser.
21
+
22
+ In the top bar, set **Workspace** to the same name you used above
23
+ (`default`, `demo`, `work`, …). If it doesn’t match, the canvas looks empty.
24
+
25
+ That’s the whole setup.
26
+
27
+ ## What you’ll see
28
+
29
+ | Area | What it does |
30
+ |---|---|
31
+ | **Graph canvas** | Zoom, pan, drag nodes; hover to highlight neighbors |
32
+ | **Search** | Find a node and center it (`/` focuses the box) |
33
+ | **Views** | Brain, Project, Architecture, Cluster, Timeline, AI Context, … |
34
+ | **Filters** | Node/edge types, project, technology, date range |
35
+ | **Node panel** | Metadata, connections, provenance, AI explanation |
36
+ | **Intelligence** | Paths, impact, orphans, duplicates, similar nodes |
37
+ | **AI Context** | Replay what retrieval would feed an LLM |
38
+ | **Workspaces** | The Engineering Brain across one, many, or every workspace |
39
+
40
+ Keyboard: `/` search · `f` fit view · `Esc` clear selection.
41
+
42
+ ## Workspaces view — workspace-aware knowledge visualization
43
+
44
+ Every other view is scoped to the single **Workspace** field in the top bar.
45
+ The **Workspaces** view is different: it lets you explore *across*
46
+ workspaces, on the model that the Engineering Brain is conceptually one
47
+ knowledge graph and a workspace is a filtering/organizational lens over it,
48
+ not a separate graph. Pick it from the view switcher to swap the filter
49
+ rail for a workspace selector + mode switch, with five lenses over the
50
+ same data:
51
+
52
+ | Mode | Shows |
53
+ |---|---|
54
+ | **Single** | One workspace's own nodes/relationships |
55
+ | **Multi** | Selected workspaces merged, with shared knowledge cross-linked and highlighted |
56
+ | **Global Brain** | Every known workspace, clustered together (like neural clusters connected by cross-workspace edges) |
57
+ | **Cross-Workspace** | Only what crosses a workspace boundary — explicit links + shared knowledge |
58
+ | **Dependency** | A workspace-level graph: one node per workspace, edges aggregate how they relate |
59
+
60
+ **Shared knowledge:** a node ingested independently into two workspaces
61
+ (e.g. the same `AuthService`) is detected by matching type + name and
62
+ rendered as one *shared* node — larger, color-blended across its
63
+ workspaces, with a slow breathing pulse on the canvas, and a workspace
64
+ badge list in the node panel. This is a heuristic (exact name+type match),
65
+ distinct from **explicit cross-workspace links** (`POST /api/workspaces/link`)
66
+ which are authoritative, purpose-created relationships between two specific
67
+ nodes (e.g. "our AuthService" `inspired_by` "the reference pattern in
68
+ workspace `oss-reference`").
69
+
70
+ **Workspace analytics** (in the rail, or `GET /api/workspaces/analytics`):
71
+ most-connected workspace, most-reused knowledge, cross-workspace
72
+ dependencies, possible duplicate knowledge, orphan workspaces, a knowledge
73
+ reuse score, and a collaboration score — all computed from your real graph
74
+ data (bounded per-workspace sampling for responsiveness), never fabricated.
75
+
76
+ **AI Context, federated:** asking a question from the Workspaces view (with
77
+ 2+ workspaces selected) replays retrieval in *every* selected workspace and
78
+ shows a per-workspace contribution bar — which workspace(s) were searched,
79
+ and what percentage of the answer came from each.
80
+
81
+ ### How this actually works (and its honest limits)
82
+
83
+ Physically, each workspace still has its own isolated embedded store
84
+ (`~/.cortex/<workspace>/`) — Kuzu is single-writer, and workspaces have
85
+ always been Cortex's storage-level isolation boundary (see
86
+ `cortex.workspace.registry`). The Workspaces view delivers the "one graph"
87
+ experience by **federating across workspaces at read time**, not by
88
+ merging their storage:
89
+
90
+ - Nodes keep their own workspace's id; "shared" is a client-visible flag
91
+ (`workspaceIds`, `shared`), not a physical merge — the underlying
92
+ duplicate nodes still exist separately, each with its own provenance.
93
+ - A synthetic `Workspace` node is injected per workspace so it can be
94
+ clustered, colored, and linked like anything else in the graph.
95
+ - Multi/Global/Cross/Dependency modes open **every selected workspace's**
96
+ database in the same process. A workspace that's locked by another
97
+ process (e.g. Cursor's `serve-mcp` integration) is skipped and reported
98
+ in `lockedWorkspaces` rather than failing the whole call.
99
+ - Shared-knowledge detection and analytics sample up to a bounded number of
100
+ nodes per workspace (`limit`, default a few thousand) for responsiveness
101
+ — at true "hundreds of workspaces / millions of nodes" scale, treat these
102
+ as directional signals, not exhaustive ones.
103
+ - Kuzu's per-database virtual memory reservation is capped
104
+ (`CORTEX_KUZU_MAX_DB_SIZE`, default 8 GiB) specifically so many workspace
105
+ databases can be open in one process at once without exhausting the OS's
106
+ mmap budget — the failure mode looks like `Buffer manager exception: Mmap
107
+ ... failed` if you ever raise this too high with many workspaces in play.
108
+
109
+ If you want a literal single merged graph (one physical store, no
110
+ per-workspace isolation) that's a separate, larger storage migration this
111
+ feature intentionally does not attempt — federation was chosen so nothing
112
+ about your existing per-workspace data or ingestion flow has to change.
113
+
114
+ ## Sample data (optional)
115
+
116
+ From a clone of this repo:
117
+
118
+ ```bash
119
+ pip install -e ".[dev]"
120
+ cortex --workspace demo ingest examples/sample_repo --project SampleApp
121
+ cortex --workspace demo serve-api
122
+ ```
123
+
124
+ Open http://localhost:8420 and set **Workspace** to `demo`.
125
+
126
+ ## Tips
127
+
128
+ - **Re-ingest** after code/docs change: `cortex ingest . --project MyApp`
129
+ Unchanged files are skipped automatically.
130
+ - **API docs** (same process): http://localhost:8420/docs
131
+ - **Custom port:** `cortex serve-api --port 9000`
132
+ - **Custom UI folder** (advanced): `export CORTEX_WEB_DIST=/path/to/dist`
133
+ - Data lives in `~/.cortex/<workspace>/` (override with `CORTEX_HOME`)
134
+ - The **Workspace** field in the top bar commits on blur/Enter, not every
135
+ keystroke — typing a workspace id auto-creates it on first use, so
136
+ committing on every keystroke used to create (and lock-probe) a phantom
137
+ workspace per partial string typed.
138
+
139
+ ## Troubleshooting: "Workspace 'X' is locked by another Cortex process"
140
+
141
+ The embedded graph DB (Kuzu) only allows **one read-write process per
142
+ workspace** at a time. If you see this error (a `503` in the graph
143
+ canvas, or in `serve-api`'s logs), something else already has that
144
+ workspace open — most commonly:
145
+
146
+ - **Cursor's Cortex MCP integration** (`cortex --workspace <ws> serve-mcp`),
147
+ started automatically whenever Cursor uses `search_knowledge`,
148
+ `get_context`, etc. against that workspace.
149
+ - **Another `serve-api` / CLI command** (`cortex ingest`, `cortex info`, …)
150
+ still running against the same workspace.
151
+
152
+ Fix: either stop the other process, or point Brain at a workspace that
153
+ isn't in use elsewhere (switch **Workspace** in the top bar). Ingesting
154
+ into a workspace that Cursor's MCP server is actively using will hit the
155
+ same conflict — pick a workspace Cursor doesn't reference, or briefly stop
156
+ the MCP server, ingest, then restart it.
157
+
158
+ ## Developing the UI itself
159
+
160
+ Only needed if you change the React app under `web/`:
161
+
162
+ ```bash
163
+ # Terminal 1
164
+ cortex --workspace demo serve-api
165
+
166
+ # Terminal 2
167
+ cd web && npm install && npm run dev
168
+ ```
169
+
170
+ Then open the Vite URL (usually http://localhost:5173).
171
+ To refresh the packaged UI that `serve-api` serves:
172
+
173
+ ```bash
174
+ ./scripts/build_brain_ui.sh
175
+ ```
176
+
177
+ ## Connect Cursor (optional)
178
+
179
+ The Brain UI explores knowledge in a browser. For AI tools inside Cursor:
180
+
181
+ ```bash
182
+ cortex ingest . --project MyApp
183
+ cortex setup project --write --project MyApp
184
+ ```
185
+
186
+ See [cursor.md](cursor.md).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cortex-kg"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Cortex: a persistent, workspace-level Engineering Knowledge Graph."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -5,4 +5,4 @@ services, decisions, people, documents, ...) rather than files. Knowledge is
5
5
  owned by the workspace and outlives any single project.
6
6
  """
7
7
 
8
- __version__ = "0.2.0"
8
+ __version__ = "0.2.2"
@@ -24,7 +24,7 @@ from pydantic import BaseModel
24
24
  from sse_starlette.sse import EventSourceResponse
25
25
 
26
26
  from cortex.config import DEFAULT_WORKSPACE
27
- from cortex.daemon import CortexDaemon, get_daemon
27
+ from cortex.daemon import CortexDaemon, WorkspaceLockedError, get_daemon
28
28
  from cortex.models import Edge, Node, Provenance
29
29
  from cortex.sync.change_feed import ChangeFeed
30
30
 
@@ -46,8 +46,35 @@ app.add_middleware(
46
46
  )
47
47
 
48
48
 
49
+ class _StripApiPrefixMiddleware:
50
+ """Rewrite ``/api/*`` → ``/*`` for Cortex Brain production builds.
51
+
52
+ The Vite dev server proxies ``/api`` and strips the prefix before
53
+ forwarding to ``cortex serve-api``. When the SPA is served as static
54
+ files from the same FastAPI process, fetches still use ``/api/...`` —
55
+ this middleware makes those routes hit the real handlers.
56
+ """
57
+
58
+ def __init__(self, app):
59
+ self.app = app
60
+
61
+ async def __call__(self, scope, receive, send):
62
+ if scope["type"] == "http":
63
+ path = scope.get("path", "")
64
+ if path == "/api" or path.startswith("/api/"):
65
+ scope = dict(scope)
66
+ scope["path"] = path[4:] if len(path) > 4 else "/"
67
+ await self.app(scope, receive, send)
68
+
69
+
70
+ app.add_middleware(_StripApiPrefixMiddleware)
71
+
72
+
49
73
  def daemon_dep(workspace: str = Query(default=DEFAULT_WORKSPACE)) -> CortexDaemon:
50
- return get_daemon(workspace)
74
+ try:
75
+ return get_daemon(workspace)
76
+ except WorkspaceLockedError as exc:
77
+ raise HTTPException(status_code=503, detail=str(exc)) from exc
51
78
 
52
79
 
53
80
  class NodeIn(BaseModel):
@@ -243,7 +270,10 @@ async def change_feed_stream(workspace: str = Query(default=DEFAULT_WORKSPACE)):
243
270
  """Server-sent events: live subscription to graph changes (roadmap
244
271
  section 10) -- any tool can react to new knowledge instead of polling.
245
272
  """
246
- daemon = get_daemon(workspace)
273
+ try:
274
+ daemon = get_daemon(workspace)
275
+ except WorkspaceLockedError as exc:
276
+ raise HTTPException(status_code=503, detail=str(exc)) from exc
247
277
  feed = ChangeFeed.instance(daemon)
248
278
  q = feed.subscribe()
249
279
 
@@ -262,8 +292,10 @@ async def change_feed_stream(workspace: str = Query(default=DEFAULT_WORKSPACE)):
262
292
 
263
293
 
264
294
  from cortex.api.graph_viz import router as graph_viz_router # noqa: E402
295
+ from cortex.api.workspace_viz import router as workspace_viz_router # noqa: E402
265
296
 
266
297
  app.include_router(graph_viz_router)
298
+ app.include_router(workspace_viz_router)
267
299
 
268
300
 
269
301
  def _resolve_brain_ui_dir() -> Optional[Path]: