kyp-mem 0.2.2 → 0.4.0
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.
- package/README.md +53 -54
- package/bin/cli.mjs +56 -1
- package/kyp_mem/__init__.py +1 -1
- package/kyp_mem/cli.py +101 -0
- package/kyp_mem/hooks.py +150 -0
- package/kyp_mem/server.py +140 -9
- package/kyp_mem/static/index.html +1438 -324
- package/kyp_mem/ui.py +135 -2
- package/package.json +2 -2
- package/pyproject.toml +2 -2
package/kyp_mem/ui.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""KYP-MEM web UI — interactive interface for browsing the vault."""
|
|
2
2
|
|
|
3
3
|
import webbrowser
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from fastapi import FastAPI
|
|
6
|
+
from fastapi import FastAPI, Request
|
|
6
7
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
7
8
|
import uvicorn
|
|
8
9
|
from .config import get_vault_path
|
|
@@ -34,6 +35,38 @@ def create_app(vault_path: str = None) -> FastAPI:
|
|
|
34
35
|
return JSONResponse({"error": "Not found"}, 404)
|
|
35
36
|
backlinks = vault.get_backlinks(path)
|
|
36
37
|
related = vault.get_related(path)
|
|
38
|
+
|
|
39
|
+
backlink_details = []
|
|
40
|
+
for bl_path in backlinks:
|
|
41
|
+
bl_note = vault.index.notes.get(bl_path)
|
|
42
|
+
context = ""
|
|
43
|
+
if bl_note:
|
|
44
|
+
for line in bl_note.content.split("\n"):
|
|
45
|
+
if note.title.lower() in line.lower() or Path(path).stem.lower() in line.lower():
|
|
46
|
+
context = line.strip()[:150]
|
|
47
|
+
break
|
|
48
|
+
backlink_details.append({
|
|
49
|
+
"path": bl_path,
|
|
50
|
+
"title": bl_note.title if bl_note else bl_path,
|
|
51
|
+
"context": context,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
unlinked = []
|
|
55
|
+
stem = Path(path).stem.lower()
|
|
56
|
+
title_lower = note.title.lower()
|
|
57
|
+
for other_path, other_note in vault.index.notes.items():
|
|
58
|
+
if other_path == path or other_path in backlinks:
|
|
59
|
+
continue
|
|
60
|
+
text = other_note.content.lower()
|
|
61
|
+
if stem in text or title_lower in text:
|
|
62
|
+
for line in other_note.content.split("\n"):
|
|
63
|
+
if stem in line.lower() or title_lower in line.lower():
|
|
64
|
+
ctx = line.strip()[:150]
|
|
65
|
+
break
|
|
66
|
+
else:
|
|
67
|
+
ctx = ""
|
|
68
|
+
unlinked.append({"path": other_path, "title": other_note.title, "context": ctx})
|
|
69
|
+
|
|
37
70
|
return JSONResponse({
|
|
38
71
|
"path": note.path,
|
|
39
72
|
"title": note.title,
|
|
@@ -43,7 +76,8 @@ def create_app(vault_path: str = None) -> FastAPI:
|
|
|
43
76
|
"created": note.created,
|
|
44
77
|
"updated": note.updated,
|
|
45
78
|
"links": note.links,
|
|
46
|
-
"backlinks":
|
|
79
|
+
"backlinks": backlink_details,
|
|
80
|
+
"unlinked": unlinked[:10],
|
|
47
81
|
"related": [{"path": p, "score": s, "title": vault.index.notes[p].title if p in vault.index.notes else p} for p, s in related],
|
|
48
82
|
})
|
|
49
83
|
|
|
@@ -75,6 +109,105 @@ def create_app(vault_path: str = None) -> FastAPI:
|
|
|
75
109
|
for n in notes
|
|
76
110
|
])
|
|
77
111
|
|
|
112
|
+
@app.post("/api/note/{path:path}")
|
|
113
|
+
async def save_note(path: str, request: Request):
|
|
114
|
+
body = await request.json()
|
|
115
|
+
content = body.get("content", "")
|
|
116
|
+
tags = body.get("tags", [])
|
|
117
|
+
props = body.get("properties", {})
|
|
118
|
+
if not path.endswith(".md"):
|
|
119
|
+
path += ".md"
|
|
120
|
+
vault.write_note(path, content, tags, props)
|
|
121
|
+
return JSONResponse({"ok": True, "path": path})
|
|
122
|
+
|
|
123
|
+
@app.post("/api/sessions/create")
|
|
124
|
+
async def create_session(request: Request):
|
|
125
|
+
body = await request.json()
|
|
126
|
+
project = body.get("project", "").strip()
|
|
127
|
+
summary = body.get("summary", "").strip()
|
|
128
|
+
if not project:
|
|
129
|
+
return JSONResponse({"error": "Project name required"}, 400)
|
|
130
|
+
session_id = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
|
131
|
+
content = (
|
|
132
|
+
f"# Session {session_id}\n\n"
|
|
133
|
+
f"**Project:** {project}\n\n"
|
|
134
|
+
f"## Summary\n{summary}\n\n"
|
|
135
|
+
f"## INVESTIGATED\n\n\n"
|
|
136
|
+
f"## LEARNED\n\n\n"
|
|
137
|
+
f"## COMPLETED\n\n\n"
|
|
138
|
+
f"## NEXT STEPS\n\n"
|
|
139
|
+
)
|
|
140
|
+
tags = ["session", "manual", project.lower().replace(" ", "-")]
|
|
141
|
+
path = f"{project}/Sessions/{session_id}.md"
|
|
142
|
+
vault.write_note(path, content, tags, {})
|
|
143
|
+
return JSONResponse({"ok": True, "path": path})
|
|
144
|
+
|
|
145
|
+
@app.get("/api/sessions")
|
|
146
|
+
def list_sessions(project: str = ""):
|
|
147
|
+
sessions = {}
|
|
148
|
+
for path, note in vault.index.notes.items():
|
|
149
|
+
if "/Sessions/" not in path and not path.startswith("Sessions/"):
|
|
150
|
+
continue
|
|
151
|
+
parts = path.split("/")
|
|
152
|
+
idx = parts.index("Sessions") if "Sessions" in parts else -1
|
|
153
|
+
proj = "/".join(parts[:idx]) if idx > 0 else "(root)"
|
|
154
|
+
if project and proj.lower() != project.lower():
|
|
155
|
+
continue
|
|
156
|
+
if proj not in sessions:
|
|
157
|
+
sessions[proj] = []
|
|
158
|
+
sessions[proj].append({
|
|
159
|
+
"path": path,
|
|
160
|
+
"title": note.title,
|
|
161
|
+
"tags": note.tags,
|
|
162
|
+
"created": note.created,
|
|
163
|
+
"updated": note.updated,
|
|
164
|
+
})
|
|
165
|
+
for proj in sessions:
|
|
166
|
+
sessions[proj].sort(key=lambda s: s["path"], reverse=True)
|
|
167
|
+
return JSONResponse(sessions)
|
|
168
|
+
|
|
169
|
+
@app.get("/api/projects")
|
|
170
|
+
def list_projects():
|
|
171
|
+
projects = set()
|
|
172
|
+
for path in vault.index.notes:
|
|
173
|
+
parts = path.split("/")
|
|
174
|
+
if len(parts) > 1:
|
|
175
|
+
projects.add(parts[0])
|
|
176
|
+
result = []
|
|
177
|
+
for proj in sorted(projects):
|
|
178
|
+
session_count = sum(1 for p in vault.index.notes if p.startswith(f"{proj}/Sessions/"))
|
|
179
|
+
result.append({"name": proj, "session_count": session_count})
|
|
180
|
+
return JSONResponse(result)
|
|
181
|
+
|
|
182
|
+
@app.delete("/api/note/{path:path}")
|
|
183
|
+
def delete_note(path: str):
|
|
184
|
+
if vault.delete(path):
|
|
185
|
+
return JSONResponse({"ok": True})
|
|
186
|
+
return JSONResponse({"error": "Not found"}, 404)
|
|
187
|
+
|
|
188
|
+
@app.post("/api/projects/create")
|
|
189
|
+
async def create_project(request: Request):
|
|
190
|
+
body = await request.json()
|
|
191
|
+
name = body.get("name", "").strip()
|
|
192
|
+
overview = body.get("overview", "").strip()
|
|
193
|
+
if not name:
|
|
194
|
+
return JSONResponse({"error": "Project name required"}, 400)
|
|
195
|
+
path = f"{name}/Knowledge.md"
|
|
196
|
+
if vault.read(path):
|
|
197
|
+
return JSONResponse({"error": "Project already exists"}, 409)
|
|
198
|
+
content = (
|
|
199
|
+
f"# {name}\n\n"
|
|
200
|
+
f"## Overview\n{overview or '(Project description, goals, tech stack)'}\n\n"
|
|
201
|
+
f"## Architecture\n(System design, key components, data flow)\n\n"
|
|
202
|
+
f"## Bugs\n### Known\n\n### Fixed\n\n\n"
|
|
203
|
+
f"## Improvements\n### Planned\n\n### Completed\n\n\n"
|
|
204
|
+
f"## Key Decisions\n(Important architectural or design decisions)\n\n"
|
|
205
|
+
f"## Notes\n(Miscellaneous project knowledge)\n"
|
|
206
|
+
)
|
|
207
|
+
tags = ["project", "knowledge", name.lower().replace(" ", "-")]
|
|
208
|
+
vault.write_note(path, content, tags, {})
|
|
209
|
+
return JSONResponse({"ok": True, "path": path})
|
|
210
|
+
|
|
78
211
|
@app.post("/api/reload")
|
|
79
212
|
def reload():
|
|
80
213
|
vault._load_all()
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kyp-mem",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Know Your Project —
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Know Your Project — Persistent knowledge base for AI agents. MCP-powered with wikilinks, backlinks, auto-learning, and neon web UI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kyp-mem": "bin/cli.mjs"
|
|
7
7
|
},
|
package/pyproject.toml
CHANGED
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kyp-mem"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Know Your Project —
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "Know Your Project — Persistent knowledge base for AI agents. MCP-powered with wikilinks, backlinks, auto-learning, and neon web UI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
11
11
|
requires-python = ">=3.10"
|