kyp-mem 0.4.3 → 0.5.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/kyp_mem/cli.py +44 -3
- package/kyp_mem/config.py +6 -0
- package/kyp_mem/hooks.py +320 -35
- package/kyp_mem/server.py +32 -9
- package/kyp_mem/static/index.html +1673 -2722
- package/kyp_mem/ui.py +90 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -0
package/kyp_mem/ui.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""KYP-MEM web UI — interactive interface for browsing the vault."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import webbrowser
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from pathlib import Path
|
|
@@ -28,6 +29,29 @@ def create_app(vault_path: str = None) -> FastAPI:
|
|
|
28
29
|
def stats():
|
|
29
30
|
return JSONResponse(vault.get_stats())
|
|
30
31
|
|
|
32
|
+
@app.get("/api/graph")
|
|
33
|
+
def graph():
|
|
34
|
+
nodes = []
|
|
35
|
+
edges = []
|
|
36
|
+
seen_edges = set()
|
|
37
|
+
for path, note in vault.index.notes.items():
|
|
38
|
+
kind = "session" if "/Sessions/" in path else "note"
|
|
39
|
+
nodes.append({"id": path, "title": note.title, "kind": kind, "tags": note.tags})
|
|
40
|
+
for link in (note.links or []):
|
|
41
|
+
target = None
|
|
42
|
+
link_lower = link.lower()
|
|
43
|
+
for p in vault.index.notes:
|
|
44
|
+
stem = p.split("/")[-1].replace(".md", "").lower()
|
|
45
|
+
if stem == link_lower:
|
|
46
|
+
target = p
|
|
47
|
+
break
|
|
48
|
+
if target and target != path:
|
|
49
|
+
key = tuple(sorted([path, target]))
|
|
50
|
+
if key not in seen_edges:
|
|
51
|
+
seen_edges.add(key)
|
|
52
|
+
edges.append({"source": path, "target": target})
|
|
53
|
+
return JSONResponse({"nodes": nodes, "edges": edges})
|
|
54
|
+
|
|
31
55
|
@app.get("/api/note/{path:path}")
|
|
32
56
|
def read_note(path: str):
|
|
33
57
|
note = vault.read(path)
|
|
@@ -177,12 +201,22 @@ def create_app(vault_path: str = None) -> FastAPI:
|
|
|
177
201
|
continue
|
|
178
202
|
if proj not in sessions:
|
|
179
203
|
sessions[proj] = []
|
|
204
|
+
summary = ""
|
|
205
|
+
lines = (note.content or "").split("\n")
|
|
206
|
+
for i, line in enumerate(lines):
|
|
207
|
+
if line.strip() == "## Summary":
|
|
208
|
+
for j in range(i + 1, len(lines)):
|
|
209
|
+
if lines[j].strip() and not lines[j].startswith("#"):
|
|
210
|
+
summary = lines[j].strip()
|
|
211
|
+
break
|
|
212
|
+
break
|
|
180
213
|
sessions[proj].append({
|
|
181
214
|
"path": path,
|
|
182
215
|
"title": note.title,
|
|
183
216
|
"tags": note.tags,
|
|
184
217
|
"created": note.created,
|
|
185
218
|
"updated": note.updated,
|
|
219
|
+
"summary": summary,
|
|
186
220
|
})
|
|
187
221
|
for proj in sessions:
|
|
188
222
|
sessions[proj].sort(key=lambda s: s["path"], reverse=True)
|
|
@@ -230,6 +264,62 @@ def create_app(vault_path: str = None) -> FastAPI:
|
|
|
230
264
|
vault.write_note(path, content, tags, {})
|
|
231
265
|
return JSONResponse({"ok": True, "path": path})
|
|
232
266
|
|
|
267
|
+
@app.get("/api/token-economics")
|
|
268
|
+
def token_economics(project: str = ""):
|
|
269
|
+
from .config import STATS_FILE
|
|
270
|
+
try:
|
|
271
|
+
raw = json.loads(STATS_FILE.read_text()) if STATS_FILE.exists() else {}
|
|
272
|
+
except (json.JSONDecodeError, OSError):
|
|
273
|
+
raw = {}
|
|
274
|
+
|
|
275
|
+
sessions = raw.get("sessions", [])
|
|
276
|
+
injections = raw.get("injections", [])
|
|
277
|
+
|
|
278
|
+
if project:
|
|
279
|
+
sessions = [s for s in sessions if s.get("project") == project]
|
|
280
|
+
injections = [i for i in injections if i.get("project") == project]
|
|
281
|
+
|
|
282
|
+
total_exploration = sum(s.get("exploration_tokens", 0) for s in sessions)
|
|
283
|
+
total_files_read = sum(s.get("files_read", 0) for s in sessions)
|
|
284
|
+
total_commands = sum(s.get("commands_run", 0) for s in sessions)
|
|
285
|
+
total_files_read_chars = sum(s.get("files_read_chars", 0) for s in sessions)
|
|
286
|
+
total_commands_chars = sum(s.get("commands_chars", 0) for s in sessions)
|
|
287
|
+
|
|
288
|
+
avg_injection_tokens = 0
|
|
289
|
+
latest_injection_tokens = 0
|
|
290
|
+
if injections:
|
|
291
|
+
avg_injection_tokens = sum(i.get("tokens", 0) for i in injections) // len(injections)
|
|
292
|
+
latest_injection_tokens = injections[-1].get("tokens", 0)
|
|
293
|
+
|
|
294
|
+
# Avg exploration per session = what a cold start costs
|
|
295
|
+
avg_exploration = total_exploration // len(sessions) if sessions else 0
|
|
296
|
+
|
|
297
|
+
# Compression ratio: how much memory compresses one session's worth
|
|
298
|
+
# of exploration into an injection. Lower = better compression.
|
|
299
|
+
# e.g. 10x means injection is 10x smaller than avg session exploration
|
|
300
|
+
compression_ratio = round(avg_exploration / latest_injection_tokens, 1) if latest_injection_tokens > 0 and avg_exploration > 0 else 0
|
|
301
|
+
|
|
302
|
+
# Per-session savings: injection replaces one cold-start exploration
|
|
303
|
+
per_session_savings_pct = 0
|
|
304
|
+
if avg_exploration > 0 and latest_injection_tokens > 0:
|
|
305
|
+
per_session_savings_pct = round((1 - latest_injection_tokens / avg_exploration) * 100, 1)
|
|
306
|
+
|
|
307
|
+
return JSONResponse({
|
|
308
|
+
"session_count": len(sessions),
|
|
309
|
+
"total_exploration_tokens": total_exploration,
|
|
310
|
+
"avg_exploration_per_session": avg_exploration,
|
|
311
|
+
"total_files_read": total_files_read,
|
|
312
|
+
"total_files_read_chars": total_files_read_chars,
|
|
313
|
+
"total_commands": total_commands,
|
|
314
|
+
"total_commands_chars": total_commands_chars,
|
|
315
|
+
"injection_count": len(injections),
|
|
316
|
+
"avg_injection_tokens": avg_injection_tokens,
|
|
317
|
+
"latest_injection_tokens": latest_injection_tokens,
|
|
318
|
+
"compression_ratio": compression_ratio,
|
|
319
|
+
"per_session_savings_pct": max(per_session_savings_pct, 0),
|
|
320
|
+
"sessions": sessions[-20:],
|
|
321
|
+
})
|
|
322
|
+
|
|
233
323
|
@app.post("/api/reload")
|
|
234
324
|
def reload():
|
|
235
325
|
vault._load_all()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kyp-mem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Know Your Project — Persistent & Session level 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"
|