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.
@@ -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}