brainpalace-cli 26.5.1__py3-none-any.whl
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.
- brainpalace_cli/__init__.py +3 -0
- brainpalace_cli/cli.py +131 -0
- brainpalace_cli/client/__init__.py +20 -0
- brainpalace_cli/client/api_client.py +552 -0
- brainpalace_cli/client/errors.py +66 -0
- brainpalace_cli/commands/__init__.py +53 -0
- brainpalace_cli/commands/cache.py +129 -0
- brainpalace_cli/commands/config.py +604 -0
- brainpalace_cli/commands/context.py +48 -0
- brainpalace_cli/commands/doctor.py +122 -0
- brainpalace_cli/commands/folders.py +282 -0
- brainpalace_cli/commands/index.py +209 -0
- brainpalace_cli/commands/init.py +530 -0
- brainpalace_cli/commands/inject.py +274 -0
- brainpalace_cli/commands/install_agent.py +340 -0
- brainpalace_cli/commands/jobs.py +345 -0
- brainpalace_cli/commands/list_cmd.py +233 -0
- brainpalace_cli/commands/mcp.py +34 -0
- brainpalace_cli/commands/memories.py +130 -0
- brainpalace_cli/commands/query.py +231 -0
- brainpalace_cli/commands/recall.py +53 -0
- brainpalace_cli/commands/remember.py +43 -0
- brainpalace_cli/commands/reset.py +83 -0
- brainpalace_cli/commands/sessions.py +79 -0
- brainpalace_cli/commands/start.py +506 -0
- brainpalace_cli/commands/status.py +239 -0
- brainpalace_cli/commands/stop.py +457 -0
- brainpalace_cli/commands/types.py +104 -0
- brainpalace_cli/commands/uninstall.py +180 -0
- brainpalace_cli/commands/whoami.py +74 -0
- brainpalace_cli/config.py +454 -0
- brainpalace_cli/config_migrate.py +198 -0
- brainpalace_cli/config_schema.py +490 -0
- brainpalace_cli/diagnostics.py +813 -0
- brainpalace_cli/discovery.py +116 -0
- brainpalace_cli/mcp_server/__init__.py +9 -0
- brainpalace_cli/mcp_server/lifecycle.py +177 -0
- brainpalace_cli/mcp_server/schemas.py +63 -0
- brainpalace_cli/mcp_server/server.py +143 -0
- brainpalace_cli/mcp_server/tools.py +227 -0
- brainpalace_cli/migration.py +117 -0
- brainpalace_cli/runtime/__init__.py +59 -0
- brainpalace_cli/runtime/claude_converter.py +155 -0
- brainpalace_cli/runtime/codex_converter.py +215 -0
- brainpalace_cli/runtime/converter_base.py +73 -0
- brainpalace_cli/runtime/gemini_converter.py +119 -0
- brainpalace_cli/runtime/opencode_converter.py +234 -0
- brainpalace_cli/runtime/parser.py +333 -0
- brainpalace_cli/runtime/skill_runtime_converter.py +234 -0
- brainpalace_cli/runtime/tool_maps.py +98 -0
- brainpalace_cli/runtime/types.py +125 -0
- brainpalace_cli/xdg_paths.py +142 -0
- brainpalace_cli-26.5.1.dist-info/METADATA +173 -0
- brainpalace_cli-26.5.1.dist-info/RECORD +56 -0
- brainpalace_cli-26.5.1.dist-info/WHEEL +4 -0
- brainpalace_cli-26.5.1.dist-info/entry_points.txt +4 -0
brainpalace_cli/cli.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Main CLI entry point for brainpalace CLI.
|
|
2
|
+
|
|
3
|
+
This module provides the command-line interface for managing and querying
|
|
4
|
+
the BrainPalace RAG server.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .commands import (
|
|
11
|
+
cache_group,
|
|
12
|
+
config_group,
|
|
13
|
+
context_command,
|
|
14
|
+
doctor_command,
|
|
15
|
+
folders_group,
|
|
16
|
+
index_command,
|
|
17
|
+
init_command,
|
|
18
|
+
inject_command,
|
|
19
|
+
install_agent_command,
|
|
20
|
+
jobs_command,
|
|
21
|
+
list_command,
|
|
22
|
+
mcp_command,
|
|
23
|
+
memories_group,
|
|
24
|
+
query_command,
|
|
25
|
+
recall_command,
|
|
26
|
+
remember_command,
|
|
27
|
+
reset_command,
|
|
28
|
+
start_command,
|
|
29
|
+
status_command,
|
|
30
|
+
stop_command,
|
|
31
|
+
submit_session_command,
|
|
32
|
+
types_group,
|
|
33
|
+
uninstall_command,
|
|
34
|
+
whoami_command,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@click.group()
|
|
39
|
+
@click.version_option(version=__version__, prog_name="brainpalace")
|
|
40
|
+
def cli() -> None:
|
|
41
|
+
"""BrainPalace CLI - Manage and query the BrainPalace RAG server.
|
|
42
|
+
|
|
43
|
+
A command-line interface for interacting with the BrainPalace document
|
|
44
|
+
indexing and semantic search API.
|
|
45
|
+
|
|
46
|
+
\b
|
|
47
|
+
Project Commands:
|
|
48
|
+
init Initialize a new brainpalace project
|
|
49
|
+
start Start the server for this project
|
|
50
|
+
stop Stop the server for this project
|
|
51
|
+
list List all running brainpalace instances
|
|
52
|
+
|
|
53
|
+
\b
|
|
54
|
+
Server Commands:
|
|
55
|
+
status Check server status
|
|
56
|
+
query Search documents
|
|
57
|
+
index Index documents from a folder
|
|
58
|
+
inject Index documents with content injection
|
|
59
|
+
jobs View and manage job queue
|
|
60
|
+
reset Clear all indexed documents
|
|
61
|
+
|
|
62
|
+
\b
|
|
63
|
+
Cache Commands:
|
|
64
|
+
cache Manage the embedding cache (status, clear)
|
|
65
|
+
|
|
66
|
+
\b
|
|
67
|
+
Folder Commands:
|
|
68
|
+
folders Manage indexed folders (list, add, remove)
|
|
69
|
+
|
|
70
|
+
\b
|
|
71
|
+
File Type Commands:
|
|
72
|
+
types List available file type presets
|
|
73
|
+
|
|
74
|
+
\b
|
|
75
|
+
MCP (Model Context Protocol):
|
|
76
|
+
mcp Serve BrainPalace over stdio for MCP-aware AI clients
|
|
77
|
+
|
|
78
|
+
\b
|
|
79
|
+
Examples:
|
|
80
|
+
brainpalace init # Initialize project
|
|
81
|
+
brainpalace start # Start server
|
|
82
|
+
brainpalace status # Check server status
|
|
83
|
+
brainpalace query "how to use python" # Search documents
|
|
84
|
+
brainpalace index ./docs # Index documents
|
|
85
|
+
brainpalace index ./src --include-type python # Index with preset
|
|
86
|
+
brainpalace inject --script enrich.py ./docs # Index with injection
|
|
87
|
+
brainpalace folders list # List indexed folders
|
|
88
|
+
brainpalace folders remove ./docs --yes # Remove folder chunks
|
|
89
|
+
brainpalace types list # Show file type presets
|
|
90
|
+
brainpalace stop # Stop server
|
|
91
|
+
|
|
92
|
+
\b
|
|
93
|
+
Environment Variables:
|
|
94
|
+
BRAINPALACE_URL Server URL (default: http://127.0.0.1:8000)
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Register project management commands
|
|
100
|
+
cli.add_command(init_command, name="init")
|
|
101
|
+
cli.add_command(start_command, name="start")
|
|
102
|
+
cli.add_command(stop_command, name="stop")
|
|
103
|
+
cli.add_command(list_command, name="list")
|
|
104
|
+
cli.add_command(whoami_command, name="whoami")
|
|
105
|
+
|
|
106
|
+
# Register server interaction commands
|
|
107
|
+
cli.add_command(doctor_command, name="doctor")
|
|
108
|
+
cli.add_command(status_command, name="status")
|
|
109
|
+
cli.add_command(query_command, name="query")
|
|
110
|
+
cli.add_command(remember_command, name="remember")
|
|
111
|
+
cli.add_command(recall_command, name="recall")
|
|
112
|
+
cli.add_command(memories_group, name="memories")
|
|
113
|
+
cli.add_command(context_command, name="context")
|
|
114
|
+
cli.add_command(submit_session_command, name="submit-session")
|
|
115
|
+
cli.add_command(index_command, name="index")
|
|
116
|
+
cli.add_command(inject_command, name="inject")
|
|
117
|
+
cli.add_command(jobs_command, name="jobs")
|
|
118
|
+
cli.add_command(reset_command, name="reset")
|
|
119
|
+
cli.add_command(config_group, name="config")
|
|
120
|
+
cli.add_command(folders_group, name="folders")
|
|
121
|
+
cli.add_command(types_group, name="types")
|
|
122
|
+
cli.add_command(cache_group, name="cache")
|
|
123
|
+
cli.add_command(uninstall_command, name="uninstall")
|
|
124
|
+
cli.add_command(install_agent_command, name="install-agent")
|
|
125
|
+
|
|
126
|
+
# Register MCP server (opt-in stdio shim for non-Claude-Code AI clients)
|
|
127
|
+
cli.add_command(mcp_command, name="mcp")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
cli()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""HTTP client for communicating with BrainPalace server."""
|
|
2
|
+
|
|
3
|
+
from .api_client import (
|
|
4
|
+
ConnectionError,
|
|
5
|
+
DocServeClient,
|
|
6
|
+
DocServeError,
|
|
7
|
+
FolderInfo,
|
|
8
|
+
ServerError,
|
|
9
|
+
)
|
|
10
|
+
from .errors import EXIT_CODE_CONNECTION_ERROR, exit_on_connection_error
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"DocServeClient",
|
|
14
|
+
"DocServeError",
|
|
15
|
+
"ConnectionError",
|
|
16
|
+
"EXIT_CODE_CONNECTION_ERROR",
|
|
17
|
+
"FolderInfo",
|
|
18
|
+
"ServerError",
|
|
19
|
+
"exit_on_connection_error",
|
|
20
|
+
]
|
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
"""HTTP client for Doc-Serve API communication."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from types import TracebackType
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DocServeError(Exception):
|
|
11
|
+
"""Base exception for Doc-Serve client errors."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConnectionError(DocServeError):
|
|
17
|
+
"""Raised when unable to connect to the server."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ServerError(DocServeError):
|
|
23
|
+
"""Raised when server returns an error response."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, message: str, status_code: int, detail: str | None = None):
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
self.status_code = status_code
|
|
28
|
+
self.detail = detail
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class HealthStatus:
|
|
33
|
+
"""Server health status."""
|
|
34
|
+
|
|
35
|
+
status: str
|
|
36
|
+
message: str | None
|
|
37
|
+
version: str
|
|
38
|
+
timestamp: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class IndexingStatus:
|
|
43
|
+
"""Detailed indexing status."""
|
|
44
|
+
|
|
45
|
+
total_documents: int
|
|
46
|
+
total_chunks: int
|
|
47
|
+
indexing_in_progress: bool
|
|
48
|
+
current_job_id: str | None
|
|
49
|
+
progress_percent: float
|
|
50
|
+
last_indexed_at: str | None
|
|
51
|
+
indexed_folders: list[str]
|
|
52
|
+
file_watcher: dict[str, Any] | None = None
|
|
53
|
+
embedding_cache: dict[str, Any] | None = None
|
|
54
|
+
migration: dict[str, Any] | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class QueryResult:
|
|
59
|
+
"""Single query result."""
|
|
60
|
+
|
|
61
|
+
text: str
|
|
62
|
+
source: str
|
|
63
|
+
score: float
|
|
64
|
+
chunk_id: str
|
|
65
|
+
metadata: dict[str, Any]
|
|
66
|
+
vector_score: float | None = None
|
|
67
|
+
bm25_score: float | None = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class QueryResponse:
|
|
72
|
+
"""Query response with results."""
|
|
73
|
+
|
|
74
|
+
results: list[QueryResult]
|
|
75
|
+
query_time_ms: float
|
|
76
|
+
total_results: int
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class FolderInfo:
|
|
81
|
+
"""Indexed folder information."""
|
|
82
|
+
|
|
83
|
+
folder_path: str
|
|
84
|
+
chunk_count: int
|
|
85
|
+
last_indexed: str
|
|
86
|
+
watch_mode: str = "off"
|
|
87
|
+
watch_debounce_seconds: int | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class IndexResponse:
|
|
92
|
+
"""Indexing operation response."""
|
|
93
|
+
|
|
94
|
+
job_id: str
|
|
95
|
+
status: str
|
|
96
|
+
message: str | None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DocServeClient:
|
|
100
|
+
"""HTTP client for Doc-Serve API."""
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
base_url: str = "http://127.0.0.1:8000",
|
|
105
|
+
timeout: float = 30.0,
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
Initialize the client.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
base_url: Server base URL.
|
|
112
|
+
timeout: Request timeout in seconds.
|
|
113
|
+
"""
|
|
114
|
+
self.base_url = base_url.rstrip("/")
|
|
115
|
+
self.timeout = timeout
|
|
116
|
+
self._client = httpx.Client(timeout=timeout)
|
|
117
|
+
|
|
118
|
+
def __enter__(self) -> "DocServeClient":
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
def __exit__(
|
|
122
|
+
self,
|
|
123
|
+
exc_type: type[BaseException] | None,
|
|
124
|
+
exc_val: BaseException | None,
|
|
125
|
+
exc_tb: TracebackType | None,
|
|
126
|
+
) -> None:
|
|
127
|
+
self.close()
|
|
128
|
+
|
|
129
|
+
def close(self) -> None:
|
|
130
|
+
"""Close the HTTP client."""
|
|
131
|
+
self._client.close()
|
|
132
|
+
|
|
133
|
+
def _request(
|
|
134
|
+
self,
|
|
135
|
+
method: str,
|
|
136
|
+
path: str,
|
|
137
|
+
json: dict[str, Any] | None = None,
|
|
138
|
+
params: dict[str, Any] | None = None,
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
"""
|
|
141
|
+
Make an HTTP request to the server.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
method: HTTP method (GET, POST, DELETE).
|
|
145
|
+
path: API path.
|
|
146
|
+
json: Optional JSON body.
|
|
147
|
+
params: Optional query parameters.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Response JSON data.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
ConnectionError: If unable to connect.
|
|
154
|
+
ServerError: If server returns an error.
|
|
155
|
+
"""
|
|
156
|
+
url = f"{self.base_url}{path}"
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
response = self._client.request(method, url, json=json, params=params)
|
|
160
|
+
except httpx.ConnectError as e:
|
|
161
|
+
raise ConnectionError(
|
|
162
|
+
f"Unable to connect to server at {self.base_url}. "
|
|
163
|
+
f"Is the server running? Error: {e}"
|
|
164
|
+
) from e
|
|
165
|
+
except httpx.TimeoutException as e:
|
|
166
|
+
raise ConnectionError(
|
|
167
|
+
f"Request timed out after {self.timeout}s. "
|
|
168
|
+
"The server may be overloaded or unresponsive."
|
|
169
|
+
) from e
|
|
170
|
+
|
|
171
|
+
if response.status_code >= 400:
|
|
172
|
+
detail = None
|
|
173
|
+
try:
|
|
174
|
+
error_data = response.json()
|
|
175
|
+
detail = error_data.get("detail", str(error_data))
|
|
176
|
+
except Exception:
|
|
177
|
+
detail = response.text
|
|
178
|
+
|
|
179
|
+
raise ServerError(
|
|
180
|
+
f"Server returned {response.status_code}",
|
|
181
|
+
status_code=response.status_code,
|
|
182
|
+
detail=detail,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
result: dict[str, Any] = response.json()
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
def health(self) -> HealthStatus:
|
|
189
|
+
"""
|
|
190
|
+
Get server health status.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
HealthStatus with current status.
|
|
194
|
+
"""
|
|
195
|
+
data = self._request("GET", "/health/")
|
|
196
|
+
return HealthStatus(
|
|
197
|
+
status=data["status"],
|
|
198
|
+
message=data.get("message"),
|
|
199
|
+
version=data.get("version", "unknown"),
|
|
200
|
+
timestamp=data.get("timestamp", ""),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def status(self) -> IndexingStatus:
|
|
204
|
+
"""
|
|
205
|
+
Get detailed indexing status.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
IndexingStatus with document counts and progress.
|
|
209
|
+
"""
|
|
210
|
+
data = self._request("GET", "/health/status")
|
|
211
|
+
return IndexingStatus(
|
|
212
|
+
total_documents=data.get("total_documents", 0),
|
|
213
|
+
total_chunks=data.get("total_chunks", 0),
|
|
214
|
+
indexing_in_progress=data.get("indexing_in_progress", False),
|
|
215
|
+
current_job_id=data.get("current_job_id"),
|
|
216
|
+
progress_percent=data.get("progress_percent", 0.0),
|
|
217
|
+
last_indexed_at=data.get("last_indexed_at"),
|
|
218
|
+
indexed_folders=data.get("indexed_folders", []),
|
|
219
|
+
file_watcher=data.get("file_watcher"),
|
|
220
|
+
embedding_cache=data.get("embedding_cache"),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def query(
|
|
224
|
+
self,
|
|
225
|
+
query_text: str,
|
|
226
|
+
top_k: int = 5,
|
|
227
|
+
similarity_threshold: float = 0.7,
|
|
228
|
+
mode: str = "hybrid",
|
|
229
|
+
alpha: float = 0.5,
|
|
230
|
+
source_types: list[str] | None = None,
|
|
231
|
+
languages: list[str] | None = None,
|
|
232
|
+
file_paths: list[str] | None = None,
|
|
233
|
+
time_decay: bool = True,
|
|
234
|
+
) -> QueryResponse:
|
|
235
|
+
"""
|
|
236
|
+
Query indexed documents.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
query_text: Search query.
|
|
240
|
+
top_k: Number of results to return.
|
|
241
|
+
similarity_threshold: Minimum similarity score.
|
|
242
|
+
mode: Retrieval mode (vector, bm25, hybrid).
|
|
243
|
+
alpha: Hybrid search weighting (1.0=vector, 0.0=bm25).
|
|
244
|
+
source_types: Filter by source types (doc, code, test).
|
|
245
|
+
languages: Filter by programming languages.
|
|
246
|
+
file_paths: Filter by file path patterns.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
QueryResponse with matching results.
|
|
250
|
+
"""
|
|
251
|
+
request_data = {
|
|
252
|
+
"query": query_text,
|
|
253
|
+
"top_k": top_k,
|
|
254
|
+
"similarity_threshold": similarity_threshold,
|
|
255
|
+
"mode": mode,
|
|
256
|
+
"alpha": alpha,
|
|
257
|
+
}
|
|
258
|
+
if not time_decay:
|
|
259
|
+
request_data["time_decay"] = False
|
|
260
|
+
if source_types is not None:
|
|
261
|
+
request_data["source_types"] = source_types
|
|
262
|
+
if languages is not None:
|
|
263
|
+
request_data["languages"] = languages
|
|
264
|
+
if file_paths is not None:
|
|
265
|
+
request_data["file_paths"] = file_paths
|
|
266
|
+
|
|
267
|
+
data = self._request("POST", "/query/", json=request_data)
|
|
268
|
+
|
|
269
|
+
results = [
|
|
270
|
+
QueryResult(
|
|
271
|
+
text=r["text"],
|
|
272
|
+
source=r["source"],
|
|
273
|
+
score=r["score"],
|
|
274
|
+
chunk_id=r["chunk_id"],
|
|
275
|
+
metadata=r.get("metadata", {}),
|
|
276
|
+
vector_score=r.get("vector_score"),
|
|
277
|
+
bm25_score=r.get("bm25_score"),
|
|
278
|
+
)
|
|
279
|
+
for r in data.get("results", [])
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
return QueryResponse(
|
|
283
|
+
results=results,
|
|
284
|
+
query_time_ms=data.get("query_time_ms", 0.0),
|
|
285
|
+
total_results=data.get("total_results", len(results)),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def index(
|
|
289
|
+
self,
|
|
290
|
+
folder_path: str,
|
|
291
|
+
chunk_size: int = 512,
|
|
292
|
+
chunk_overlap: int = 50,
|
|
293
|
+
recursive: bool = True,
|
|
294
|
+
include_code: bool = False,
|
|
295
|
+
supported_languages: list[str] | None = None,
|
|
296
|
+
code_chunk_strategy: str = "ast_aware",
|
|
297
|
+
include_patterns: list[str] | None = None,
|
|
298
|
+
exclude_patterns: list[str] | None = None,
|
|
299
|
+
include_types: list[str] | None = None,
|
|
300
|
+
generate_summaries: bool = False,
|
|
301
|
+
force: bool = False,
|
|
302
|
+
allow_external: bool = False,
|
|
303
|
+
injector_script: str | None = None,
|
|
304
|
+
folder_metadata_file: str | None = None,
|
|
305
|
+
dry_run: bool = False,
|
|
306
|
+
watch_mode: str | None = None,
|
|
307
|
+
watch_debounce_seconds: int | None = None,
|
|
308
|
+
) -> IndexResponse:
|
|
309
|
+
"""
|
|
310
|
+
Enqueue an indexing job for documents and optionally code from a folder.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
folder_path: Path to folder with documents.
|
|
314
|
+
chunk_size: Target chunk size in tokens.
|
|
315
|
+
chunk_overlap: Overlap between chunks.
|
|
316
|
+
recursive: Whether to scan recursively.
|
|
317
|
+
include_code: Whether to index source code files.
|
|
318
|
+
supported_languages: Languages to index (defaults to all).
|
|
319
|
+
code_chunk_strategy: Strategy for code chunking.
|
|
320
|
+
include_patterns: Additional include patterns.
|
|
321
|
+
exclude_patterns: Additional exclude patterns.
|
|
322
|
+
include_types: File type preset names (e.g., ["python", "docs"]).
|
|
323
|
+
generate_summaries: Generate LLM summaries for code chunks.
|
|
324
|
+
force: Bypass deduplication and force a new job.
|
|
325
|
+
allow_external: Allow paths outside the project directory.
|
|
326
|
+
injector_script: Path to Python script exporting process_chunk().
|
|
327
|
+
folder_metadata_file: Path to JSON file with static metadata.
|
|
328
|
+
dry_run: Validate injector against sample chunks without indexing.
|
|
329
|
+
watch_mode: Watch mode for auto-reindex: 'auto' or 'off'.
|
|
330
|
+
watch_debounce_seconds: Per-folder debounce in seconds.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
IndexResponse with job ID and queue status.
|
|
334
|
+
"""
|
|
335
|
+
body: dict[str, Any] = {
|
|
336
|
+
"folder_path": folder_path,
|
|
337
|
+
"chunk_size": chunk_size,
|
|
338
|
+
"chunk_overlap": chunk_overlap,
|
|
339
|
+
"recursive": recursive,
|
|
340
|
+
"include_code": include_code,
|
|
341
|
+
"supported_languages": supported_languages,
|
|
342
|
+
"code_chunk_strategy": code_chunk_strategy,
|
|
343
|
+
"include_patterns": include_patterns,
|
|
344
|
+
"exclude_patterns": exclude_patterns,
|
|
345
|
+
"generate_summaries": generate_summaries,
|
|
346
|
+
"force": force,
|
|
347
|
+
}
|
|
348
|
+
if include_types is not None:
|
|
349
|
+
body["include_types"] = include_types
|
|
350
|
+
if injector_script is not None:
|
|
351
|
+
body["injector_script"] = injector_script
|
|
352
|
+
if folder_metadata_file is not None:
|
|
353
|
+
body["folder_metadata_file"] = folder_metadata_file
|
|
354
|
+
if dry_run:
|
|
355
|
+
body["dry_run"] = True
|
|
356
|
+
if watch_mode is not None:
|
|
357
|
+
body["watch_mode"] = watch_mode
|
|
358
|
+
if watch_debounce_seconds is not None:
|
|
359
|
+
body["watch_debounce_seconds"] = watch_debounce_seconds
|
|
360
|
+
|
|
361
|
+
data = self._request(
|
|
362
|
+
"POST",
|
|
363
|
+
"/index/",
|
|
364
|
+
json=body,
|
|
365
|
+
params={"force": force, "allow_external": allow_external},
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
return IndexResponse(
|
|
369
|
+
job_id=data["job_id"],
|
|
370
|
+
status=data["status"],
|
|
371
|
+
message=data.get("message"),
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def list_folders(self) -> list[FolderInfo]:
|
|
375
|
+
"""
|
|
376
|
+
List all indexed folders.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
List of FolderInfo objects sorted by folder path.
|
|
380
|
+
"""
|
|
381
|
+
data = self._request("GET", "/index/folders/")
|
|
382
|
+
folders: list[FolderInfo] = [
|
|
383
|
+
FolderInfo(
|
|
384
|
+
folder_path=f["folder_path"],
|
|
385
|
+
chunk_count=f["chunk_count"],
|
|
386
|
+
last_indexed=f["last_indexed"],
|
|
387
|
+
watch_mode=f.get("watch_mode", "off"),
|
|
388
|
+
watch_debounce_seconds=f.get("watch_debounce_seconds"),
|
|
389
|
+
)
|
|
390
|
+
for f in data.get("folders", [])
|
|
391
|
+
]
|
|
392
|
+
return folders
|
|
393
|
+
|
|
394
|
+
def delete_folder(self, folder_path: str) -> dict[str, Any]:
|
|
395
|
+
"""
|
|
396
|
+
Delete all indexed chunks for a folder.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
folder_path: Absolute path to the folder to remove.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Response dict with folder_path, chunks_deleted, and message.
|
|
403
|
+
"""
|
|
404
|
+
result: dict[str, Any] = self._request(
|
|
405
|
+
"DELETE",
|
|
406
|
+
"/index/folders/",
|
|
407
|
+
json={"folder_path": folder_path},
|
|
408
|
+
)
|
|
409
|
+
return result
|
|
410
|
+
|
|
411
|
+
def reset(self) -> IndexResponse:
|
|
412
|
+
"""
|
|
413
|
+
Reset the index by deleting all documents.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
IndexResponse confirming reset.
|
|
417
|
+
"""
|
|
418
|
+
data = self._request("DELETE", "/index/")
|
|
419
|
+
|
|
420
|
+
return IndexResponse(
|
|
421
|
+
job_id=data.get("job_id", "reset"),
|
|
422
|
+
status=data["status"],
|
|
423
|
+
message=data.get("message"),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def list_jobs(self, limit: int = 20) -> list[dict[str, Any]]:
|
|
427
|
+
"""
|
|
428
|
+
List jobs in the queue.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
limit: Maximum number of jobs to return.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
List of job dictionaries.
|
|
435
|
+
"""
|
|
436
|
+
data = self._request("GET", f"/index/jobs/?limit={limit}")
|
|
437
|
+
jobs: list[dict[str, Any]] = data.get("jobs", [])
|
|
438
|
+
return jobs
|
|
439
|
+
|
|
440
|
+
def get_job(self, job_id: str) -> dict[str, Any]:
|
|
441
|
+
"""
|
|
442
|
+
Get details for a specific job.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
job_id: The job ID to look up.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Job detail dictionary.
|
|
449
|
+
"""
|
|
450
|
+
return self._request("GET", f"/index/jobs/{job_id}")
|
|
451
|
+
|
|
452
|
+
def cancel_job(self, job_id: str) -> dict[str, Any]:
|
|
453
|
+
"""
|
|
454
|
+
Cancel a specific job.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
job_id: The job ID to cancel.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Cancellation result dictionary.
|
|
461
|
+
"""
|
|
462
|
+
return self._request("DELETE", f"/index/jobs/{job_id}")
|
|
463
|
+
|
|
464
|
+
def cache_status(self) -> dict[str, Any]:
|
|
465
|
+
"""
|
|
466
|
+
Get embedding cache status.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Dict with hits, misses, hit_rate, mem_entries, entry_count, size_bytes.
|
|
470
|
+
|
|
471
|
+
Raises:
|
|
472
|
+
ConnectionError: If unable to connect.
|
|
473
|
+
ServerError: If server returns an error.
|
|
474
|
+
"""
|
|
475
|
+
return self._request("GET", "/index/cache/")
|
|
476
|
+
|
|
477
|
+
def clear_cache(self) -> dict[str, Any]:
|
|
478
|
+
"""
|
|
479
|
+
Clear the embedding cache.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
Dict with count, size_bytes, size_mb of cleared entries.
|
|
483
|
+
|
|
484
|
+
Raises:
|
|
485
|
+
ConnectionError: If unable to connect.
|
|
486
|
+
ServerError: If server returns an error.
|
|
487
|
+
"""
|
|
488
|
+
return self._request("DELETE", "/index/cache/")
|
|
489
|
+
|
|
490
|
+
# ----- Curated memory namespace (Phase 030) -------------------------
|
|
491
|
+
|
|
492
|
+
def remember(
|
|
493
|
+
self,
|
|
494
|
+
text: str,
|
|
495
|
+
section: str = "Notes",
|
|
496
|
+
tags: list[str] | None = None,
|
|
497
|
+
origin: str = "user",
|
|
498
|
+
) -> dict[str, Any]:
|
|
499
|
+
"""Create a curated memory."""
|
|
500
|
+
return self._request(
|
|
501
|
+
"POST",
|
|
502
|
+
"/memories/",
|
|
503
|
+
json={
|
|
504
|
+
"text": text,
|
|
505
|
+
"section": section,
|
|
506
|
+
"tags": tags or [],
|
|
507
|
+
"origin": origin,
|
|
508
|
+
},
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
def recall(self, query: str, top_k: int = 5) -> dict[str, Any]:
|
|
512
|
+
"""Recall from the memory namespace only."""
|
|
513
|
+
return self._request(
|
|
514
|
+
"POST", "/memories/recall", json={"query": query, "top_k": top_k}
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
def list_memories(
|
|
518
|
+
self,
|
|
519
|
+
tag: str | None = None,
|
|
520
|
+
section: str | None = None,
|
|
521
|
+
include_obsolete: bool = False,
|
|
522
|
+
) -> dict[str, Any]:
|
|
523
|
+
"""List curated memories."""
|
|
524
|
+
params: dict[str, Any] = {"include_obsolete": include_obsolete}
|
|
525
|
+
if tag:
|
|
526
|
+
params["tag"] = tag
|
|
527
|
+
if section:
|
|
528
|
+
params["section"] = section
|
|
529
|
+
return self._request("GET", "/memories/", params=params)
|
|
530
|
+
|
|
531
|
+
def delete_memory(self, memory_id: str) -> dict[str, Any]:
|
|
532
|
+
"""Delete a curated memory by id."""
|
|
533
|
+
return self._request("DELETE", f"/memories/{memory_id}")
|
|
534
|
+
|
|
535
|
+
def obsolete_memory(
|
|
536
|
+
self, memory_id: str, superseded_by: str | None = None
|
|
537
|
+
) -> dict[str, Any]:
|
|
538
|
+
"""Mark a curated memory obsolete."""
|
|
539
|
+
params = {"superseded_by": superseded_by} if superseded_by else None
|
|
540
|
+
return self._request(
|
|
541
|
+
"POST", f"/memories/{memory_id}/obsolete", params=params
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
# ----- Session-start context (Phase 035) ---------------------------
|
|
545
|
+
|
|
546
|
+
def session_context(self) -> dict[str, Any]:
|
|
547
|
+
"""Fetch the session-start context block."""
|
|
548
|
+
return self._request("GET", "/context/session-start")
|
|
549
|
+
|
|
550
|
+
def submit_session_extract(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
551
|
+
"""Persist a session extraction payload (Phase 060)."""
|
|
552
|
+
return self._request("POST", "/sessions/extract", json=payload)
|