kyp-mem 0.4.4 → 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 +32 -0
- package/kyp_mem/config.py +6 -0
- package/kyp_mem/hooks.py +244 -34
- package/kyp_mem/server.py +32 -9
- package/kyp_mem/static/index.html +1673 -2740
- package/kyp_mem/ui.py +80 -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)
|
|
@@ -240,6 +264,62 @@ def create_app(vault_path: str = None) -> FastAPI:
|
|
|
240
264
|
vault.write_note(path, content, tags, {})
|
|
241
265
|
return JSONResponse({"ok": True, "path": path})
|
|
242
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
|
+
|
|
243
323
|
@app.post("/api/reload")
|
|
244
324
|
def reload():
|
|
245
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"
|