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.
Files changed (56) hide show
  1. brainpalace_cli/__init__.py +3 -0
  2. brainpalace_cli/cli.py +131 -0
  3. brainpalace_cli/client/__init__.py +20 -0
  4. brainpalace_cli/client/api_client.py +552 -0
  5. brainpalace_cli/client/errors.py +66 -0
  6. brainpalace_cli/commands/__init__.py +53 -0
  7. brainpalace_cli/commands/cache.py +129 -0
  8. brainpalace_cli/commands/config.py +604 -0
  9. brainpalace_cli/commands/context.py +48 -0
  10. brainpalace_cli/commands/doctor.py +122 -0
  11. brainpalace_cli/commands/folders.py +282 -0
  12. brainpalace_cli/commands/index.py +209 -0
  13. brainpalace_cli/commands/init.py +530 -0
  14. brainpalace_cli/commands/inject.py +274 -0
  15. brainpalace_cli/commands/install_agent.py +340 -0
  16. brainpalace_cli/commands/jobs.py +345 -0
  17. brainpalace_cli/commands/list_cmd.py +233 -0
  18. brainpalace_cli/commands/mcp.py +34 -0
  19. brainpalace_cli/commands/memories.py +130 -0
  20. brainpalace_cli/commands/query.py +231 -0
  21. brainpalace_cli/commands/recall.py +53 -0
  22. brainpalace_cli/commands/remember.py +43 -0
  23. brainpalace_cli/commands/reset.py +83 -0
  24. brainpalace_cli/commands/sessions.py +79 -0
  25. brainpalace_cli/commands/start.py +506 -0
  26. brainpalace_cli/commands/status.py +239 -0
  27. brainpalace_cli/commands/stop.py +457 -0
  28. brainpalace_cli/commands/types.py +104 -0
  29. brainpalace_cli/commands/uninstall.py +180 -0
  30. brainpalace_cli/commands/whoami.py +74 -0
  31. brainpalace_cli/config.py +454 -0
  32. brainpalace_cli/config_migrate.py +198 -0
  33. brainpalace_cli/config_schema.py +490 -0
  34. brainpalace_cli/diagnostics.py +813 -0
  35. brainpalace_cli/discovery.py +116 -0
  36. brainpalace_cli/mcp_server/__init__.py +9 -0
  37. brainpalace_cli/mcp_server/lifecycle.py +177 -0
  38. brainpalace_cli/mcp_server/schemas.py +63 -0
  39. brainpalace_cli/mcp_server/server.py +143 -0
  40. brainpalace_cli/mcp_server/tools.py +227 -0
  41. brainpalace_cli/migration.py +117 -0
  42. brainpalace_cli/runtime/__init__.py +59 -0
  43. brainpalace_cli/runtime/claude_converter.py +155 -0
  44. brainpalace_cli/runtime/codex_converter.py +215 -0
  45. brainpalace_cli/runtime/converter_base.py +73 -0
  46. brainpalace_cli/runtime/gemini_converter.py +119 -0
  47. brainpalace_cli/runtime/opencode_converter.py +234 -0
  48. brainpalace_cli/runtime/parser.py +333 -0
  49. brainpalace_cli/runtime/skill_runtime_converter.py +234 -0
  50. brainpalace_cli/runtime/tool_maps.py +98 -0
  51. brainpalace_cli/runtime/types.py +125 -0
  52. brainpalace_cli/xdg_paths.py +142 -0
  53. brainpalace_cli-26.5.1.dist-info/METADATA +173 -0
  54. brainpalace_cli-26.5.1.dist-info/RECORD +56 -0
  55. brainpalace_cli-26.5.1.dist-info/WHEEL +4 -0
  56. brainpalace_cli-26.5.1.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,3 @@
1
+ """Doc-Serve CLI - Command-line interface for managing Doc-Serve server."""
2
+
3
+ __version__ = "26.5.1"
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)