deja-cli 0.1.0__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.
- deja/__init__.py +0 -0
- deja/config.py +127 -0
- deja/core/__init__.py +0 -0
- deja/core/extractor.py +135 -0
- deja/core/reflection.py +364 -0
- deja/core/scheduler.py +65 -0
- deja/core/store.py +1413 -0
- deja/ingest/__init__.py +0 -0
- deja/ingest/watchers/__init__.py +0 -0
- deja/ingest/watchers/base.py +143 -0
- deja/ingest/watchers/claude_code.py +62 -0
- deja/ingest/watchers/codex_cli.py +95 -0
- deja/ingest/watchers/gemini_cli.py +96 -0
- deja/interfaces/__init__.py +0 -0
- deja/interfaces/cli.py +1967 -0
- deja/interfaces/mcp_server.py +96 -0
- deja/interfaces/web.py +104 -0
- deja/interfaces/web_ui/index.html +614 -0
- deja/llm/__init__.py +0 -0
- deja/llm/base.py +34 -0
- deja/llm/embedding.py +45 -0
- deja/llm/factory.py +90 -0
- deja/llm/providers/__init__.py +0 -0
- deja/llm/providers/anthropic.py +21 -0
- deja/llm/providers/ollama.py +30 -0
- deja/main.py +4 -0
- deja_cli-0.1.0.dist-info/METADATA +100 -0
- deja_cli-0.1.0.dist-info/RECORD +31 -0
- deja_cli-0.1.0.dist-info/WHEEL +4 -0
- deja_cli-0.1.0.dist-info/entry_points.txt +3 -0
- deja_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""MCP server for deja — thin wrappers over core. No business logic here."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from mcp.server.fastmcp import FastMCP
|
|
8
|
+
|
|
9
|
+
from deja.config import load_config
|
|
10
|
+
from deja.core.store import MemoryStore
|
|
11
|
+
from deja.interfaces.cli import _format_load_result
|
|
12
|
+
from deja.llm.factory import create_embedding_adapter
|
|
13
|
+
|
|
14
|
+
mcp = FastMCP("deja")
|
|
15
|
+
|
|
16
|
+
_store: Optional[MemoryStore] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def _get_store() -> MemoryStore:
|
|
20
|
+
global _store
|
|
21
|
+
if _store is None:
|
|
22
|
+
_store = MemoryStore(load_config())
|
|
23
|
+
await _store.init_db()
|
|
24
|
+
return _store
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@mcp.tool()
|
|
28
|
+
async def memory_load(project: Optional[str] = None, context: Optional[str] = None) -> str:
|
|
29
|
+
"""Load memories at session start. Returns type-budgeted memories as compact text
|
|
30
|
+
(top-5 per type for gotcha/decision/preference/pattern, top-3 procedures by reuse,
|
|
31
|
+
top-3 recent progress). Call this at the beginning of every session.
|
|
32
|
+
|
|
33
|
+
context: task description to re-rank memories by relevance instead of raw confidence.
|
|
34
|
+
Always pass a short summary of what the user wants to work on — e.g. "fix auth bug in
|
|
35
|
+
login flow" or "add dark mode to dashboard". Without context, memories are sorted by
|
|
36
|
+
raw confidence and the most relevant ones may not surface.
|
|
37
|
+
"""
|
|
38
|
+
store = await _get_store()
|
|
39
|
+
config = load_config()
|
|
40
|
+
embedding_adapter = await create_embedding_adapter(config) if context else None
|
|
41
|
+
result = await store.load_budgeted(project, context=context, embedding_adapter=embedding_adapter)
|
|
42
|
+
return _format_load_result(result)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@mcp.tool()
|
|
46
|
+
async def memory_save(
|
|
47
|
+
content: str,
|
|
48
|
+
type: str,
|
|
49
|
+
project: Optional[str] = None,
|
|
50
|
+
confidence: float = 1.0,
|
|
51
|
+
category: str = "agent",
|
|
52
|
+
domain: Optional[str] = None,
|
|
53
|
+
) -> str:
|
|
54
|
+
"""Save a memory to the vault. Deduplicates automatically — if a near-identical
|
|
55
|
+
memory already exists, its confidence is bumped instead of inserting a duplicate.
|
|
56
|
+
|
|
57
|
+
type: preference | pattern | decision | gotcha | progress | procedure
|
|
58
|
+
category: user (preferences/habits) | agent (operational knowledge)
|
|
59
|
+
domain: debug | build | test | deploy | research (optional, for procedure type)
|
|
60
|
+
"""
|
|
61
|
+
store = await _get_store()
|
|
62
|
+
mem_id = await store.save(
|
|
63
|
+
{
|
|
64
|
+
"content": content,
|
|
65
|
+
"type": type,
|
|
66
|
+
"project": project,
|
|
67
|
+
"scope": f"project:{project}" if project else "global",
|
|
68
|
+
"confidence": confidence,
|
|
69
|
+
"category": category,
|
|
70
|
+
"domain": domain,
|
|
71
|
+
"source": "mcp",
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
return json.dumps({"id": mem_id, "status": "saved"})
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@mcp.tool()
|
|
78
|
+
async def memory_search(
|
|
79
|
+
query: str,
|
|
80
|
+
project: Optional[str] = None,
|
|
81
|
+
type: Optional[str] = None,
|
|
82
|
+
limit: int = 10,
|
|
83
|
+
) -> str:
|
|
84
|
+
"""Full-text search over active memories. Use when looking for specific knowledge
|
|
85
|
+
mid-session. With project: searches global + that project. Without: all scopes."""
|
|
86
|
+
store = await _get_store()
|
|
87
|
+
results = await store.search(query, project, mem_type=type, limit=limit)
|
|
88
|
+
return json.dumps(results, default=str)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def main() -> None:
|
|
92
|
+
mcp.run()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
main()
|
deja/interfaces/web.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Web viewer backend for the deja memory vault.
|
|
2
|
+
|
|
3
|
+
Serves a single-page HTML UI and a minimal JSON API.
|
|
4
|
+
Started by `deja viewer`; not imported elsewhere.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from contextlib import asynccontextmanager
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from fastapi import FastAPI, HTTPException, Query
|
|
13
|
+
from fastapi.responses import HTMLResponse
|
|
14
|
+
|
|
15
|
+
from deja.config import load_config
|
|
16
|
+
from deja.core.store import MemoryStore
|
|
17
|
+
|
|
18
|
+
_store: Optional[MemoryStore] = None
|
|
19
|
+
_HTML = Path(__file__).parent / "web_ui" / "index.html"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_store() -> MemoryStore:
|
|
23
|
+
assert _store is not None, "Store not initialised"
|
|
24
|
+
return _store
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@asynccontextmanager
|
|
28
|
+
async def lifespan(app: FastAPI):
|
|
29
|
+
global _store
|
|
30
|
+
config = load_config()
|
|
31
|
+
_store = MemoryStore(config)
|
|
32
|
+
await _store.init_db()
|
|
33
|
+
yield
|
|
34
|
+
await _store.close()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
app = FastAPI(title="deja vault", lifespan=lifespan)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ── UI ────────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
|
43
|
+
async def root():
|
|
44
|
+
return _HTML.read_text(encoding="utf-8")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ── API ───────────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
@app.get("/api/memories")
|
|
50
|
+
async def list_memories(
|
|
51
|
+
project: Optional[str] = Query(None),
|
|
52
|
+
type: Optional[str] = Query(None),
|
|
53
|
+
status: str = Query("active"),
|
|
54
|
+
limit: int = Query(300, ge=1, le=2000),
|
|
55
|
+
offset: int = Query(0, ge=0),
|
|
56
|
+
):
|
|
57
|
+
memories, total = await _get_store().list_filtered(
|
|
58
|
+
project=project, mem_type=type, status=status, limit=limit, offset=offset
|
|
59
|
+
)
|
|
60
|
+
return {"memories": memories, "total": total}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.get("/api/memories/{memory_id}")
|
|
64
|
+
async def get_memory(memory_id: str):
|
|
65
|
+
mem = await _get_store().get(memory_id)
|
|
66
|
+
if not mem:
|
|
67
|
+
raise HTTPException(404, "Memory not found")
|
|
68
|
+
return mem
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.get("/api/projects")
|
|
72
|
+
async def list_projects():
|
|
73
|
+
projects = await _get_store().list_projects()
|
|
74
|
+
return {"projects": projects}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@app.get("/api/stats")
|
|
78
|
+
async def get_stats(project: Optional[str] = Query(None)):
|
|
79
|
+
return await _get_store().get_stats(project)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.get("/api/search")
|
|
83
|
+
async def search_memories(
|
|
84
|
+
q: str = Query(..., min_length=1),
|
|
85
|
+
project: Optional[str] = Query(None),
|
|
86
|
+
type: Optional[str] = Query(None),
|
|
87
|
+
limit: int = Query(50, ge=1, le=200),
|
|
88
|
+
):
|
|
89
|
+
memories = await _get_store().search(
|
|
90
|
+
q, project=project, mem_type=type, limit=limit, track_usage=False
|
|
91
|
+
)
|
|
92
|
+
return {"memories": memories, "query": q}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.post("/api/memories/{memory_id}/archive")
|
|
96
|
+
async def archive_memory(memory_id: str):
|
|
97
|
+
await _get_store().archive(memory_id)
|
|
98
|
+
return {"ok": True}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@app.post("/api/memories/{memory_id}/invalidate")
|
|
102
|
+
async def invalidate_memory(memory_id: str):
|
|
103
|
+
await _get_store().invalidate(memory_id)
|
|
104
|
+
return {"ok": True}
|