agent-brain-rag 1.1.0__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 (30) hide show
  1. agent_brain_rag-1.1.0/PKG-INFO +202 -0
  2. agent_brain_rag-1.1.0/README.md +161 -0
  3. agent_brain_rag-1.1.0/doc_serve_server/__init__.py +3 -0
  4. agent_brain_rag-1.1.0/doc_serve_server/api/__init__.py +5 -0
  5. agent_brain_rag-1.1.0/doc_serve_server/api/main.py +332 -0
  6. agent_brain_rag-1.1.0/doc_serve_server/api/routers/__init__.py +11 -0
  7. agent_brain_rag-1.1.0/doc_serve_server/api/routers/health.py +100 -0
  8. agent_brain_rag-1.1.0/doc_serve_server/api/routers/index.py +208 -0
  9. agent_brain_rag-1.1.0/doc_serve_server/api/routers/query.py +96 -0
  10. agent_brain_rag-1.1.0/doc_serve_server/config/__init__.py +5 -0
  11. agent_brain_rag-1.1.0/doc_serve_server/config/settings.py +92 -0
  12. agent_brain_rag-1.1.0/doc_serve_server/indexing/__init__.py +19 -0
  13. agent_brain_rag-1.1.0/doc_serve_server/indexing/bm25_index.py +166 -0
  14. agent_brain_rag-1.1.0/doc_serve_server/indexing/chunking.py +831 -0
  15. agent_brain_rag-1.1.0/doc_serve_server/indexing/document_loader.py +506 -0
  16. agent_brain_rag-1.1.0/doc_serve_server/indexing/embedding.py +274 -0
  17. agent_brain_rag-1.1.0/doc_serve_server/locking.py +133 -0
  18. agent_brain_rag-1.1.0/doc_serve_server/models/__init__.py +18 -0
  19. agent_brain_rag-1.1.0/doc_serve_server/models/health.py +126 -0
  20. agent_brain_rag-1.1.0/doc_serve_server/models/index.py +157 -0
  21. agent_brain_rag-1.1.0/doc_serve_server/models/query.py +191 -0
  22. agent_brain_rag-1.1.0/doc_serve_server/project_root.py +85 -0
  23. agent_brain_rag-1.1.0/doc_serve_server/runtime.py +112 -0
  24. agent_brain_rag-1.1.0/doc_serve_server/services/__init__.py +11 -0
  25. agent_brain_rag-1.1.0/doc_serve_server/services/indexing_service.py +476 -0
  26. agent_brain_rag-1.1.0/doc_serve_server/services/query_service.py +414 -0
  27. agent_brain_rag-1.1.0/doc_serve_server/storage/__init__.py +5 -0
  28. agent_brain_rag-1.1.0/doc_serve_server/storage/vector_store.py +320 -0
  29. agent_brain_rag-1.1.0/doc_serve_server/storage_paths.py +72 -0
  30. agent_brain_rag-1.1.0/pyproject.toml +87 -0
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.3
2
+ Name: agent-brain-rag
3
+ Version: 1.1.0
4
+ Summary: RAG-based document indexing and semantic search server for AI agents
5
+ License: MIT
6
+ Keywords: rag,semantic-search,documentation,indexing,llama-index,chromadb,ai-agent,brain
7
+ Author: Spillwave Solutions
8
+ Requires-Python: >=3.10,<4.0
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Documentation
18
+ Classifier: Topic :: Text Processing :: Indexing
19
+ Requires-Dist: anthropic (>=0.40.0,<0.41.0)
20
+ Requires-Dist: chromadb (>=0.5.0,<0.6.0)
21
+ Requires-Dist: click (>=8.1.0,<9.0.0)
22
+ Requires-Dist: fastapi (>=0.115.0,<0.116.0)
23
+ Requires-Dist: llama-index-core (>=0.14.0,<0.15.0)
24
+ Requires-Dist: llama-index-embeddings-openai (>=0.5.0,<0.6.0)
25
+ Requires-Dist: llama-index-llms-openai (>=0.6.12,<0.7.0)
26
+ Requires-Dist: llama-index-readers-file (>=0.5.0,<0.6.0)
27
+ Requires-Dist: llama-index-retrievers-bm25 (>=0.6.0,<0.7.0)
28
+ Requires-Dist: openai (>=1.57.0,<2.0.0)
29
+ Requires-Dist: pydantic (>=2.10.0,<3.0.0)
30
+ Requires-Dist: pydantic-settings (>=2.6.0,<3.0.0)
31
+ Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
32
+ Requires-Dist: rank-bm25 (>=0.2.2,<0.3.0)
33
+ Requires-Dist: tiktoken (>=0.8.0,<0.9.0)
34
+ Requires-Dist: tree-sitter-language-pack (>=0.7.3,<0.8.0)
35
+ Requires-Dist: uvicorn[standard] (>=0.32.0,<0.33.0)
36
+ Project-URL: Documentation, https://github.com/SpillwaveSolutions/doc-serve#readme
37
+ Project-URL: Homepage, https://github.com/SpillwaveSolutions/doc-serve
38
+ Project-URL: Repository, https://github.com/SpillwaveSolutions/doc-serve
39
+ Description-Content-Type: text/markdown
40
+
41
+ # Doc-Serve Server
42
+
43
+ RAG-based document indexing and semantic search REST API service.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install doc-serve
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ 1. Set environment variables:
54
+ ```bash
55
+ export OPENAI_API_KEY=your-key
56
+ export ANTHROPIC_API_KEY=your-key
57
+ ```
58
+
59
+ 2. Start the server:
60
+ ```bash
61
+ doc-serve
62
+ ```
63
+
64
+ The server will start at `http://127.0.0.1:8000`.
65
+
66
+ ## Features
67
+
68
+ - **Document Indexing**: Load and index documents from folders (PDF, Markdown, TXT, DOCX, HTML)
69
+ - **Context-Aware Chunking**: Smart text splitting with configurable chunk sizes and overlap
70
+ - **Semantic Search**: Query indexed documents using natural language
71
+ - **OpenAI Embeddings**: Uses `text-embedding-3-large` for high-quality embeddings
72
+ - **Chroma Vector Store**: Persistent, thread-safe vector database
73
+ - **FastAPI**: Modern, high-performance REST API with OpenAPI documentation
74
+
75
+ ## Quick Start
76
+
77
+ ### Prerequisites
78
+
79
+ - Python 3.10+
80
+ - Poetry
81
+ - OpenAI API key
82
+
83
+ ### Installation
84
+
85
+ ```bash
86
+ cd doc-serve-server
87
+ poetry install
88
+ ```
89
+
90
+ ### Configuration
91
+
92
+ Copy the environment template and configure:
93
+
94
+ ```bash
95
+ cp ../.env.example .env
96
+ # Edit .env with your API keys
97
+ ```
98
+
99
+ Required environment variables:
100
+ - `OPENAI_API_KEY`: Your OpenAI API key for embeddings
101
+
102
+ ### Running the Server
103
+
104
+ ```bash
105
+ # Development mode
106
+ poetry run uvicorn doc_serve_server.api.main:app --reload
107
+
108
+ # Or use the entry point
109
+ poetry run doc-serve
110
+ ```
111
+
112
+ The server will start at `http://127.0.0.1:8000`.
113
+
114
+ ### API Documentation
115
+
116
+ Once running, visit:
117
+ - Swagger UI: http://127.0.0.1:8000/docs
118
+ - ReDoc: http://127.0.0.1:8000/redoc
119
+ - OpenAPI JSON: http://127.0.0.1:8000/openapi.json
120
+
121
+ ## API Endpoints
122
+
123
+ ### Health
124
+
125
+ - `GET /health` - Server health status
126
+ - `GET /health/status` - Detailed indexing status
127
+
128
+ ### Indexing
129
+
130
+ - `POST /index` - Start indexing documents from a folder
131
+ - `POST /index/add` - Add documents to existing index
132
+ - `DELETE /index` - Reset the index
133
+
134
+ ### Querying
135
+
136
+ - `POST /query` - Semantic search query
137
+ - `GET /query/count` - Get indexed document count
138
+
139
+ ## Example Usage
140
+
141
+ ### Index Documents
142
+
143
+ ```bash
144
+ curl -X POST http://localhost:8000/index \
145
+ -H "Content-Type: application/json" \
146
+ -d '{"folder_path": "/path/to/docs"}'
147
+ ```
148
+
149
+ ### Query Documents
150
+
151
+ ```bash
152
+ curl -X POST http://localhost:8000/query \
153
+ -H "Content-Type: application/json" \
154
+ -d '{"query": "How do I configure authentication?", "top_k": 5}'
155
+ ```
156
+
157
+ ## Architecture
158
+
159
+ ```
160
+ doc_serve_server/
161
+ ├── api/
162
+ │ ├── main.py # FastAPI application
163
+ │ └── routers/ # Endpoint handlers
164
+ ├── config/
165
+ │ └── settings.py # Configuration management
166
+ ├── models/ # Pydantic request/response models
167
+ ├── indexing/
168
+ │ ├── document_loader.py # Document loading
169
+ │ ├── chunking.py # Text chunking
170
+ │ └── embedding.py # Embedding generation
171
+ ├── services/
172
+ │ ├── indexing_service.py # Indexing orchestration
173
+ │ └── query_service.py # Query execution
174
+ └── storage/
175
+ └── vector_store.py # Chroma vector store
176
+ ```
177
+
178
+ ## Development
179
+
180
+ ### Running Tests
181
+
182
+ ```bash
183
+ poetry run pytest
184
+ ```
185
+
186
+ ### Code Formatting
187
+
188
+ ```bash
189
+ poetry run black doc_serve_server/
190
+ poetry run ruff check doc_serve_server/
191
+ ```
192
+
193
+ ### Type Checking
194
+
195
+ ```bash
196
+ poetry run mypy doc_serve_server/
197
+ ```
198
+
199
+ ## License
200
+
201
+ MIT
202
+
@@ -0,0 +1,161 @@
1
+ # Doc-Serve Server
2
+
3
+ RAG-based document indexing and semantic search REST API service.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install doc-serve
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ 1. Set environment variables:
14
+ ```bash
15
+ export OPENAI_API_KEY=your-key
16
+ export ANTHROPIC_API_KEY=your-key
17
+ ```
18
+
19
+ 2. Start the server:
20
+ ```bash
21
+ doc-serve
22
+ ```
23
+
24
+ The server will start at `http://127.0.0.1:8000`.
25
+
26
+ ## Features
27
+
28
+ - **Document Indexing**: Load and index documents from folders (PDF, Markdown, TXT, DOCX, HTML)
29
+ - **Context-Aware Chunking**: Smart text splitting with configurable chunk sizes and overlap
30
+ - **Semantic Search**: Query indexed documents using natural language
31
+ - **OpenAI Embeddings**: Uses `text-embedding-3-large` for high-quality embeddings
32
+ - **Chroma Vector Store**: Persistent, thread-safe vector database
33
+ - **FastAPI**: Modern, high-performance REST API with OpenAPI documentation
34
+
35
+ ## Quick Start
36
+
37
+ ### Prerequisites
38
+
39
+ - Python 3.10+
40
+ - Poetry
41
+ - OpenAI API key
42
+
43
+ ### Installation
44
+
45
+ ```bash
46
+ cd doc-serve-server
47
+ poetry install
48
+ ```
49
+
50
+ ### Configuration
51
+
52
+ Copy the environment template and configure:
53
+
54
+ ```bash
55
+ cp ../.env.example .env
56
+ # Edit .env with your API keys
57
+ ```
58
+
59
+ Required environment variables:
60
+ - `OPENAI_API_KEY`: Your OpenAI API key for embeddings
61
+
62
+ ### Running the Server
63
+
64
+ ```bash
65
+ # Development mode
66
+ poetry run uvicorn doc_serve_server.api.main:app --reload
67
+
68
+ # Or use the entry point
69
+ poetry run doc-serve
70
+ ```
71
+
72
+ The server will start at `http://127.0.0.1:8000`.
73
+
74
+ ### API Documentation
75
+
76
+ Once running, visit:
77
+ - Swagger UI: http://127.0.0.1:8000/docs
78
+ - ReDoc: http://127.0.0.1:8000/redoc
79
+ - OpenAPI JSON: http://127.0.0.1:8000/openapi.json
80
+
81
+ ## API Endpoints
82
+
83
+ ### Health
84
+
85
+ - `GET /health` - Server health status
86
+ - `GET /health/status` - Detailed indexing status
87
+
88
+ ### Indexing
89
+
90
+ - `POST /index` - Start indexing documents from a folder
91
+ - `POST /index/add` - Add documents to existing index
92
+ - `DELETE /index` - Reset the index
93
+
94
+ ### Querying
95
+
96
+ - `POST /query` - Semantic search query
97
+ - `GET /query/count` - Get indexed document count
98
+
99
+ ## Example Usage
100
+
101
+ ### Index Documents
102
+
103
+ ```bash
104
+ curl -X POST http://localhost:8000/index \
105
+ -H "Content-Type: application/json" \
106
+ -d '{"folder_path": "/path/to/docs"}'
107
+ ```
108
+
109
+ ### Query Documents
110
+
111
+ ```bash
112
+ curl -X POST http://localhost:8000/query \
113
+ -H "Content-Type: application/json" \
114
+ -d '{"query": "How do I configure authentication?", "top_k": 5}'
115
+ ```
116
+
117
+ ## Architecture
118
+
119
+ ```
120
+ doc_serve_server/
121
+ ├── api/
122
+ │ ├── main.py # FastAPI application
123
+ │ └── routers/ # Endpoint handlers
124
+ ├── config/
125
+ │ └── settings.py # Configuration management
126
+ ├── models/ # Pydantic request/response models
127
+ ├── indexing/
128
+ │ ├── document_loader.py # Document loading
129
+ │ ├── chunking.py # Text chunking
130
+ │ └── embedding.py # Embedding generation
131
+ ├── services/
132
+ │ ├── indexing_service.py # Indexing orchestration
133
+ │ └── query_service.py # Query execution
134
+ └── storage/
135
+ └── vector_store.py # Chroma vector store
136
+ ```
137
+
138
+ ## Development
139
+
140
+ ### Running Tests
141
+
142
+ ```bash
143
+ poetry run pytest
144
+ ```
145
+
146
+ ### Code Formatting
147
+
148
+ ```bash
149
+ poetry run black doc_serve_server/
150
+ poetry run ruff check doc_serve_server/
151
+ ```
152
+
153
+ ### Type Checking
154
+
155
+ ```bash
156
+ poetry run mypy doc_serve_server/
157
+ ```
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1,3 @@
1
+ """Doc-Serve Server - RAG-based document indexing and query service."""
2
+
3
+ __version__ = "1.1.0"
@@ -0,0 +1,5 @@
1
+ """FastAPI application and routers."""
2
+
3
+ from .main import app, run
4
+
5
+ __all__ = ["app", "run"]
@@ -0,0 +1,332 @@
1
+ """FastAPI application entry point."""
2
+
3
+ import logging
4
+ import os
5
+ import socket
6
+ from collections.abc import AsyncIterator
7
+ from contextlib import asynccontextmanager
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import click
12
+ import uvicorn
13
+ from fastapi import FastAPI
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+
16
+ from doc_serve_server import __version__
17
+ from doc_serve_server.config import settings
18
+ from doc_serve_server.indexing.bm25_index import BM25IndexManager
19
+ from doc_serve_server.locking import acquire_lock, cleanup_stale, is_stale, release_lock
20
+ from doc_serve_server.project_root import resolve_project_root
21
+ from doc_serve_server.runtime import RuntimeState, delete_runtime, write_runtime
22
+ from doc_serve_server.services import IndexingService, QueryService
23
+ from doc_serve_server.storage import VectorStoreManager
24
+ from doc_serve_server.storage_paths import resolve_state_dir, resolve_storage_paths
25
+
26
+ from .routers import health_router, index_router, query_router
27
+
28
+ # Configure logging
29
+ logging.basicConfig(
30
+ level=logging.DEBUG if settings.DEBUG else logging.INFO,
31
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
32
+ )
33
+ logger = logging.getLogger(__name__)
34
+
35
+ # Module-level state for multi-instance mode
36
+ _runtime_state: Optional[RuntimeState] = None
37
+ _state_dir: Optional[Path] = None
38
+
39
+
40
+ @asynccontextmanager
41
+ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
42
+ """Application lifespan manager.
43
+
44
+ Initializes services and stores them on app.state for dependency
45
+ injection via request.app.state in route handlers.
46
+
47
+ In per-project mode:
48
+ - Resolves project root and state directory
49
+ - Acquires lock (with stale detection)
50
+ - Writes runtime.json with server info
51
+ - Cleans up on shutdown
52
+ """
53
+ global _runtime_state, _state_dir
54
+
55
+ logger.info("Starting Doc-Serve server...")
56
+
57
+ if settings.OPENAI_API_KEY:
58
+ os.environ["OPENAI_API_KEY"] = settings.OPENAI_API_KEY
59
+
60
+ # Determine mode and resolve paths
61
+ mode = settings.DOC_SERVE_MODE
62
+ state_dir = _state_dir # May be set by CLI
63
+ storage_paths: Optional[dict[str, Path]] = None
64
+
65
+ if state_dir is not None:
66
+ # Per-project mode with explicit state directory
67
+ mode = "project"
68
+
69
+ # Check for stale locks and clean up
70
+ if is_stale(state_dir):
71
+ logger.info(f"Cleaning stale lock in {state_dir}")
72
+ cleanup_stale(state_dir)
73
+
74
+ # Acquire exclusive lock
75
+ if not acquire_lock(state_dir):
76
+ raise RuntimeError(
77
+ f"Another doc-serve instance is already running for {state_dir}"
78
+ )
79
+
80
+ # Resolve storage paths (creates directories)
81
+ storage_paths = resolve_storage_paths(state_dir)
82
+ logger.info(f"State directory: {state_dir}")
83
+
84
+ try:
85
+ # Determine persistence directories
86
+ chroma_dir = (
87
+ str(storage_paths["chroma_db"])
88
+ if storage_paths
89
+ else settings.CHROMA_PERSIST_DIR
90
+ )
91
+ bm25_dir = (
92
+ str(storage_paths["bm25_index"])
93
+ if storage_paths
94
+ else settings.BM25_INDEX_PATH
95
+ )
96
+
97
+ # Initialize services and store on app.state for DI
98
+ vector_store = VectorStoreManager(
99
+ persist_dir=chroma_dir,
100
+ )
101
+ await vector_store.initialize()
102
+ app.state.vector_store = vector_store
103
+ logger.info("Vector store initialized")
104
+
105
+ bm25_manager = BM25IndexManager(
106
+ persist_dir=bm25_dir,
107
+ )
108
+ bm25_manager.initialize()
109
+ app.state.bm25_manager = bm25_manager
110
+ logger.info("BM25 index manager initialized")
111
+
112
+ # Create indexing service with injected deps
113
+ indexing_service = IndexingService(
114
+ vector_store=vector_store,
115
+ bm25_manager=bm25_manager,
116
+ )
117
+ app.state.indexing_service = indexing_service
118
+
119
+ # Create query service with injected deps
120
+ query_service = QueryService(
121
+ vector_store=vector_store,
122
+ bm25_manager=bm25_manager,
123
+ )
124
+ app.state.query_service = query_service
125
+
126
+ # Set multi-instance metadata on app.state for health endpoint
127
+ app.state.mode = mode
128
+ app.state.instance_id = _runtime_state.instance_id if _runtime_state else None
129
+ app.state.project_id = _runtime_state.project_id if _runtime_state else None
130
+ app.state.active_projects = None # For shared mode (future)
131
+
132
+ except Exception as e:
133
+ logger.error(f"Failed to initialize services: {e}")
134
+ # Clean up lock if we acquired it
135
+ if state_dir is not None:
136
+ release_lock(state_dir)
137
+ raise
138
+
139
+ yield
140
+
141
+ logger.info("Shutting down Doc-Serve server...")
142
+
143
+ # Cleanup for per-project mode
144
+ if state_dir is not None:
145
+ delete_runtime(state_dir)
146
+ release_lock(state_dir)
147
+ logger.info(f"Released lock and cleaned up state in {state_dir}")
148
+
149
+
150
+ # Create FastAPI application
151
+ app = FastAPI(
152
+ title="Doc-Serve API",
153
+ description=(
154
+ "RAG-based document indexing and semantic search API. "
155
+ "Index documents from folders and query them using natural language."
156
+ ),
157
+ version="1.1.0",
158
+ lifespan=lifespan,
159
+ docs_url="/docs",
160
+ redoc_url="/redoc",
161
+ openapi_url="/openapi.json",
162
+ )
163
+
164
+ # Add CORS middleware
165
+ app.add_middleware(
166
+ CORSMiddleware,
167
+ allow_origins=["*"], # Configure appropriately for production
168
+ allow_credentials=True,
169
+ allow_methods=["*"],
170
+ allow_headers=["*"],
171
+ )
172
+
173
+ # Include routers
174
+ app.include_router(health_router, prefix="/health", tags=["Health"])
175
+ app.include_router(index_router, prefix="/index", tags=["Indexing"])
176
+ app.include_router(query_router, prefix="/query", tags=["Querying"])
177
+
178
+
179
+ @app.get("/", include_in_schema=False)
180
+ async def root() -> dict[str, str]:
181
+ """Root endpoint redirects to docs."""
182
+ return {
183
+ "name": "Doc-Serve API",
184
+ "version": "1.1.0",
185
+ "docs": "/docs",
186
+ "health": "/health",
187
+ }
188
+
189
+
190
+ def _find_free_port() -> int:
191
+ """Find a free port by binding to port 0.
192
+
193
+ Returns:
194
+ An available port number.
195
+ """
196
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
197
+ s.bind(("", 0))
198
+ s.listen(1)
199
+ port = s.getsockname()[1]
200
+ return port # type: ignore[no-any-return]
201
+
202
+
203
+ def run(
204
+ host: Optional[str] = None,
205
+ port: Optional[int] = None,
206
+ reload: Optional[bool] = None,
207
+ state_dir: Optional[str] = None,
208
+ ) -> None:
209
+ """Run the server using uvicorn.
210
+
211
+ Args:
212
+ host: Host to bind to (default: from settings)
213
+ port: Port to bind to (default: from settings, 0 = auto-assign)
214
+ reload: Enable auto-reload (default: from DEBUG setting)
215
+ state_dir: State directory for per-project mode (enables locking)
216
+ """
217
+ global _runtime_state, _state_dir
218
+
219
+ resolved_host = host or settings.API_HOST
220
+ resolved_port = port if port is not None else settings.API_PORT
221
+
222
+ # Handle port 0: find a free port
223
+ if resolved_port == 0:
224
+ resolved_port = _find_free_port()
225
+ logger.info(f"Auto-assigned port: {resolved_port}")
226
+
227
+ # Set up per-project mode if state_dir specified
228
+ if state_dir:
229
+ _state_dir = Path(state_dir).resolve()
230
+
231
+ # Create runtime state
232
+ _runtime_state = RuntimeState(
233
+ mode="project",
234
+ project_root=str(_state_dir.parent.parent.parent), # .claude/doc-serve
235
+ bind_host=resolved_host,
236
+ port=resolved_port,
237
+ pid=os.getpid(),
238
+ base_url=f"http://{resolved_host}:{resolved_port}",
239
+ )
240
+
241
+ # Write runtime.json before starting server
242
+ # Note: Lock is acquired in lifespan, but we write runtime early
243
+ # for port discovery by CLI tools
244
+ _state_dir.mkdir(parents=True, exist_ok=True)
245
+ write_runtime(_state_dir, _runtime_state)
246
+ logger.info(f"Per-project mode enabled: {_state_dir}")
247
+
248
+ uvicorn.run(
249
+ "doc_serve_server.api.main:app",
250
+ host=resolved_host,
251
+ port=resolved_port,
252
+ reload=reload if reload is not None else settings.DEBUG,
253
+ )
254
+
255
+
256
+ @click.command()
257
+ @click.version_option(version=__version__, prog_name="doc-serve")
258
+ @click.option(
259
+ "--host",
260
+ "-h",
261
+ default=None,
262
+ help=f"Host to bind to (default: {settings.API_HOST})",
263
+ )
264
+ @click.option(
265
+ "--port",
266
+ "-p",
267
+ type=int,
268
+ default=None,
269
+ help=f"Port to bind to (default: {settings.API_PORT}, 0 = auto-assign)",
270
+ )
271
+ @click.option(
272
+ "--reload/--no-reload",
273
+ default=None,
274
+ help=f"Enable auto-reload (default: {'enabled' if settings.DEBUG else 'disabled'})",
275
+ )
276
+ @click.option(
277
+ "--state-dir",
278
+ "-s",
279
+ default=None,
280
+ help="State directory for per-project mode (enables locking and runtime.json)",
281
+ )
282
+ @click.option(
283
+ "--project-dir",
284
+ "-d",
285
+ default=None,
286
+ help="Project directory (auto-resolves state-dir to .claude/doc-serve)",
287
+ )
288
+ def cli(
289
+ host: Optional[str],
290
+ port: Optional[int],
291
+ reload: Optional[bool],
292
+ state_dir: Optional[str],
293
+ project_dir: Optional[str],
294
+ ) -> None:
295
+ """Doc-Serve Server - RAG-based document indexing and semantic search API.
296
+
297
+ Start the FastAPI server for document indexing and querying.
298
+
299
+ \b
300
+ Examples:
301
+ doc-serve # Start with default settings
302
+ doc-serve --port 8080 # Start on port 8080
303
+ doc-serve --port 0 # Auto-assign an available port
304
+ doc-serve --host 0.0.0.0 # Bind to all interfaces
305
+ doc-serve --reload # Enable auto-reload
306
+ doc-serve --project-dir /my/project # Per-project mode (auto state-dir)
307
+ doc-serve --state-dir /path/.claude/doc-serve # Explicit state directory
308
+
309
+ \b
310
+ Environment Variables:
311
+ API_HOST Server host (default: 127.0.0.1)
312
+ API_PORT Server port (default: 8000)
313
+ DEBUG Enable debug mode (default: false)
314
+ DOC_SERVE_STATE_DIR Override state directory
315
+ DOC_SERVE_MODE Instance mode: 'project' or 'shared'
316
+ """
317
+ # Resolve state directory from options
318
+ resolved_state_dir = state_dir
319
+
320
+ if project_dir and not state_dir:
321
+ # Auto-resolve state-dir from project directory
322
+ project_root = resolve_project_root(Path(project_dir))
323
+ resolved_state_dir = str(resolve_state_dir(project_root))
324
+ elif settings.DOC_SERVE_STATE_DIR and not state_dir:
325
+ # Use environment variable if set
326
+ resolved_state_dir = settings.DOC_SERVE_STATE_DIR
327
+
328
+ run(host=host, port=port, reload=reload, state_dir=resolved_state_dir)
329
+
330
+
331
+ if __name__ == "__main__":
332
+ cli()