kyp-mem 0.2.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/LICENSE +21 -0
- package/README.md +147 -0
- package/bin/cli.mjs +33 -0
- package/bin/install.mjs +36 -0
- package/kyp_mem/__init__.py +3 -0
- package/kyp_mem/__pycache__/__init__.cpython-314.pyc +0 -0
- package/kyp_mem/__pycache__/cli.cpython-314.pyc +0 -0
- package/kyp_mem/__pycache__/config.cpython-314.pyc +0 -0
- package/kyp_mem/__pycache__/ui.cpython-314.pyc +0 -0
- package/kyp_mem/__pycache__/vault.cpython-314.pyc +0 -0
- package/kyp_mem/cli.py +263 -0
- package/kyp_mem/config.py +31 -0
- package/kyp_mem/server.py +166 -0
- package/kyp_mem/static/index.html +1293 -0
- package/kyp_mem/ui.py +91 -0
- package/kyp_mem/vault.py +319 -0
- package/package.json +35 -0
- package/pyproject.toml +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adhithya-Karthikeyan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# KYP-MEM — Know Your Project Memory
|
|
2
|
+
|
|
3
|
+
**Headless Obsidian for AI agents.** A markdown knowledge base with wikilinks, backlinks, tags, related notes, and a neon web UI — all powered by an MCP server so Claude (or any AI) can read and write your project knowledge directly.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install kyp-mem
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup (3 commands)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 1. Choose where your vault (knowledge base) lives
|
|
15
|
+
kyp-mem init
|
|
16
|
+
|
|
17
|
+
# 2. Connect to Claude Code — auto-configures MCP
|
|
18
|
+
kyp-mem setup-claude
|
|
19
|
+
|
|
20
|
+
# 3. Restart Claude Code — done!
|
|
21
|
+
# kyp-mem now runs headlessly every session.
|
|
22
|
+
# Claude can read/write/search your knowledge base.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That's it. Claude now has `kyp_read`, `kyp_write`, `kyp_search`, and 7 other tools available in every session.
|
|
26
|
+
|
|
27
|
+
## Optional: Web UI
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
kyp-mem ui
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Opens an Obsidian-like interface at `localhost:3333` with:
|
|
34
|
+
- Collapsible folder tree
|
|
35
|
+
- Rendered markdown with syntax highlighting
|
|
36
|
+
- Clickable `[[wikilinks]]`
|
|
37
|
+
- Backlinks and related notes panel
|
|
38
|
+
- Interactive D3 graph view (toggleable)
|
|
39
|
+
- Full-text search (`Cmd+K`)
|
|
40
|
+
- Draggable resizable panels
|
|
41
|
+
|
|
42
|
+
## How It Works
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
|
|
46
|
+
│ Claude Code │──stdio──▶│ kyp-mem │──read/──▶│ ~/.kyp-mem/ │
|
|
47
|
+
│ (any AI) │◀─────────│ MCP server │ write │ vault/ │
|
|
48
|
+
└─────────────┘ └─────────────┘ │ *.md files │
|
|
49
|
+
└──────────────┘
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Headless by default** — runs as an MCP server (stdio), no GUI needed
|
|
53
|
+
- **Markdown files on disk** — same format as Obsidian, no database
|
|
54
|
+
- **In-memory index** — links, backlinks, tags, search, similarity scoring
|
|
55
|
+
- **Web UI optional** — `kyp-mem ui` when you want to browse visually
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
|
|
59
|
+
| Command | What it does |
|
|
60
|
+
|---------|-------------|
|
|
61
|
+
| `kyp-mem init` | First-time setup — choose vault location |
|
|
62
|
+
| `kyp-mem setup-claude` | Auto-configure Claude Code MCP settings |
|
|
63
|
+
| `kyp-mem setup-claude --global` | Configure globally (all projects) |
|
|
64
|
+
| `kyp-mem serve` | Start MCP server (used by Claude, not you) |
|
|
65
|
+
| `kyp-mem ui` | Open web UI at localhost:3333 |
|
|
66
|
+
| `kyp-mem stats` | Print vault statistics |
|
|
67
|
+
| `kyp-mem tree` | Print vault tree |
|
|
68
|
+
| `kyp-mem doctor` | Check installation health |
|
|
69
|
+
|
|
70
|
+
## MCP Tools (what Claude gets)
|
|
71
|
+
|
|
72
|
+
| Tool | Description |
|
|
73
|
+
|------|-------------|
|
|
74
|
+
| `kyp_list` | Browse vault folders and notes |
|
|
75
|
+
| `kyp_read` | Read a note — content + tags + backlinks + related |
|
|
76
|
+
| `kyp_write` | Create or update a note with tags and properties |
|
|
77
|
+
| `kyp_delete` | Delete a note |
|
|
78
|
+
| `kyp_search` | Full-text search across all notes |
|
|
79
|
+
| `kyp_tags` | List all tags or filter notes by tag |
|
|
80
|
+
| `kyp_related` | Find related notes by links, tags, proximity |
|
|
81
|
+
| `kyp_recent` | Recently modified notes |
|
|
82
|
+
| `kyp_stats` | Vault statistics |
|
|
83
|
+
|
|
84
|
+
## Note Format
|
|
85
|
+
|
|
86
|
+
Standard markdown with YAML frontmatter — same as Obsidian:
|
|
87
|
+
|
|
88
|
+
```markdown
|
|
89
|
+
---
|
|
90
|
+
tags: [project, trading, config]
|
|
91
|
+
source: config.py
|
|
92
|
+
created: 2026-05-12
|
|
93
|
+
updated: 2026-05-12
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
# Configuration
|
|
97
|
+
|
|
98
|
+
Settings are defined in `HedgeConfig`. See [[Risk Management]] for safety checks.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`[[Wikilinks]]` are automatically parsed, indexed, and turned into navigable backlinks.
|
|
102
|
+
|
|
103
|
+
## Manual Claude Code Config
|
|
104
|
+
|
|
105
|
+
If you prefer to configure manually instead of using `setup-claude`:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"mcpServers": {
|
|
110
|
+
"kyp-mem": {
|
|
111
|
+
"command": "kyp-mem",
|
|
112
|
+
"args": ["serve"],
|
|
113
|
+
"env": {
|
|
114
|
+
"KYP_VAULT": "~/.kyp-mem/vault"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Add to `~/.claude/settings.json` (global) or `.claude/settings.json` (per-project).
|
|
122
|
+
|
|
123
|
+
## Architecture
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
~/.kyp-mem/
|
|
127
|
+
├── config.json # vault path + settings
|
|
128
|
+
└── vault/ # your knowledge base
|
|
129
|
+
├── Project A/
|
|
130
|
+
│ ├── Architecture.md
|
|
131
|
+
│ ├── Configuration.md
|
|
132
|
+
│ └── Bugs.md
|
|
133
|
+
└── Project B/
|
|
134
|
+
└── ...
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Publishing to PyPI
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pip install build twine
|
|
141
|
+
python3 -m build
|
|
142
|
+
twine upload dist/*
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync, execSync } from "child_process";
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
|
|
7
|
+
function tryRun(cmd, cmdArgs) {
|
|
8
|
+
try {
|
|
9
|
+
execFileSync(cmd, cmdArgs, { stdio: "inherit" });
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Try the installed kyp-mem binary first
|
|
17
|
+
if (tryRun("kyp-mem", args)) process.exit(0);
|
|
18
|
+
|
|
19
|
+
// Fallback: python3 -m kyp_mem.cli
|
|
20
|
+
if (tryRun("python3", ["-m", "kyp_mem.cli", ...args])) process.exit(0);
|
|
21
|
+
|
|
22
|
+
// Fallback: python -m kyp_mem.cli
|
|
23
|
+
if (tryRun("python", ["-m", "kyp_mem.cli", ...args])) process.exit(0);
|
|
24
|
+
|
|
25
|
+
console.error("");
|
|
26
|
+
console.error(" \x1b[31mError:\x1b[0m kyp-mem Python package not found.");
|
|
27
|
+
console.error("");
|
|
28
|
+
console.error(" Install it:");
|
|
29
|
+
console.error(" \x1b[33mpip install kyp-mem\x1b[0m");
|
|
30
|
+
console.error(" Or from source:");
|
|
31
|
+
console.error(" \x1b[33mpip install git+https://github.com/Adhithya-Karthikeyan/KYP-MEM.git\x1b[0m");
|
|
32
|
+
console.error("");
|
|
33
|
+
process.exit(1);
|
package/bin/install.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname, resolve } from "path";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const root = resolve(__dirname, "..");
|
|
9
|
+
|
|
10
|
+
function tryInstall(cmd) {
|
|
11
|
+
try {
|
|
12
|
+
execSync(cmd, { cwd: root, stdio: "pipe" });
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check if already installed
|
|
20
|
+
try {
|
|
21
|
+
execSync("kyp-mem --help", { stdio: "pipe" });
|
|
22
|
+
process.exit(0);
|
|
23
|
+
} catch {}
|
|
24
|
+
|
|
25
|
+
console.log(" Installing kyp-mem Python package...");
|
|
26
|
+
|
|
27
|
+
if (tryInstall("pip install --user .")) {
|
|
28
|
+
console.log(" \x1b[32m✓\x1b[0m kyp-mem installed successfully");
|
|
29
|
+
} else if (tryInstall("pip3 install --user .")) {
|
|
30
|
+
console.log(" \x1b[32m✓\x1b[0m kyp-mem installed successfully");
|
|
31
|
+
} else if (tryInstall("python3 -m pip install --user .")) {
|
|
32
|
+
console.log(" \x1b[32m✓\x1b[0m kyp-mem installed successfully");
|
|
33
|
+
} else {
|
|
34
|
+
console.log(" \x1b[33m!\x1b[0m Could not auto-install Python package.");
|
|
35
|
+
console.log(" \x1b[33m!\x1b[0m Run manually: pip install kyp-mem");
|
|
36
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/kyp_mem/cli.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""KYP-MEM CLI — Know Your Project Memory."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import argparse
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
C = "\033[36m" # cyan
|
|
10
|
+
G = "\033[32m" # green
|
|
11
|
+
Y = "\033[33m" # yellow
|
|
12
|
+
D = "\033[90m" # dim
|
|
13
|
+
R = "\033[0m" # reset
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
parser = argparse.ArgumentParser(
|
|
18
|
+
prog="kyp-mem",
|
|
19
|
+
description="KYP-MEM — Know Your Project Memory. Headless Obsidian for AI agents.",
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument("--vault", default=None, help="Override vault path")
|
|
22
|
+
|
|
23
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
24
|
+
|
|
25
|
+
subparsers.add_parser("init", help="Set up vault location (first-time setup)")
|
|
26
|
+
subparsers.add_parser("serve", help="Start MCP server (stdio)")
|
|
27
|
+
|
|
28
|
+
sc = subparsers.add_parser("setup-claude", help="Auto-configure Claude Code to use KYP-MEM")
|
|
29
|
+
sc.add_argument("--global", dest="global_config", action="store_true",
|
|
30
|
+
help="Add to global ~/.claude/settings.json (default: project .claude/settings.json)")
|
|
31
|
+
|
|
32
|
+
ui_parser = subparsers.add_parser("ui", help="Open web UI in browser")
|
|
33
|
+
ui_parser.add_argument("--port", type=int, default=3333, help="Port (default: 3333)")
|
|
34
|
+
ui_parser.add_argument("--no-open", action="store_true", help="Don't auto-open browser")
|
|
35
|
+
|
|
36
|
+
subparsers.add_parser("stats", help="Print vault statistics")
|
|
37
|
+
subparsers.add_parser("tree", help="Print vault tree")
|
|
38
|
+
subparsers.add_parser("doctor", help="Check installation and config health")
|
|
39
|
+
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
|
|
42
|
+
if args.vault:
|
|
43
|
+
os.environ["KYP_VAULT"] = args.vault
|
|
44
|
+
|
|
45
|
+
if args.command == "init":
|
|
46
|
+
_run_init()
|
|
47
|
+
elif args.command == "serve":
|
|
48
|
+
from .server import mcp
|
|
49
|
+
mcp.run()
|
|
50
|
+
elif args.command == "setup-claude":
|
|
51
|
+
_run_setup_claude(global_config=args.global_config)
|
|
52
|
+
elif args.command == "ui":
|
|
53
|
+
from .ui import start_ui
|
|
54
|
+
start_ui(port=args.port, open_browser=not args.no_open)
|
|
55
|
+
elif args.command == "stats":
|
|
56
|
+
_run_stats()
|
|
57
|
+
elif args.command == "tree":
|
|
58
|
+
_run_tree()
|
|
59
|
+
elif args.command == "doctor":
|
|
60
|
+
_run_doctor()
|
|
61
|
+
else:
|
|
62
|
+
_print_banner()
|
|
63
|
+
parser.print_help()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _run_init():
|
|
67
|
+
from .config import CONFIG_FILE, save_config, load_config, DEFAULT_VAULT
|
|
68
|
+
|
|
69
|
+
print()
|
|
70
|
+
print(f"{C} ██╗ ██╗██╗ ██╗██████╗ ███╗ ███╗███████╗███╗ ███╗{R}")
|
|
71
|
+
print(f"{C} ██║ ██╔╝╚██╗ ██╔╝██╔══██╗ ████╗ ████║██╔════╝████╗ ████║{R}")
|
|
72
|
+
print(f"{C} █████╔╝ ╚████╔╝ ██████╔╝█████╗██╔████╔██║█████╗ ██╔████╔██║{R}")
|
|
73
|
+
print(f"{C} ██╔═██╗ ╚██╔╝ ██╔═══╝ ╚════╝██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║{R}")
|
|
74
|
+
print(f"{C} ██║ ██╗ ██║ ██║ ██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║{R}")
|
|
75
|
+
print(f"{C} ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝{R}")
|
|
76
|
+
print()
|
|
77
|
+
print(f" {D}Know Your Project — Headless Obsidian for AI agents{R}")
|
|
78
|
+
print()
|
|
79
|
+
print(f" {Y}>> First-time setup{R}")
|
|
80
|
+
print()
|
|
81
|
+
|
|
82
|
+
current = load_config()
|
|
83
|
+
current_path = current.get("vault_path", DEFAULT_VAULT)
|
|
84
|
+
|
|
85
|
+
print(f" Where should your vault live?")
|
|
86
|
+
print(f" {D}This is where all your notes/knowledge will be stored.{R}")
|
|
87
|
+
print(f" {D}Default: {current_path}{R}")
|
|
88
|
+
print()
|
|
89
|
+
|
|
90
|
+
vault_input = input(f" Vault path [{current_path}]: ").strip()
|
|
91
|
+
vault_path = vault_input or current_path
|
|
92
|
+
vault_path = str(Path(vault_path).expanduser().resolve())
|
|
93
|
+
|
|
94
|
+
Path(vault_path).mkdir(parents=True, exist_ok=True)
|
|
95
|
+
save_config({"vault_path": vault_path})
|
|
96
|
+
|
|
97
|
+
print()
|
|
98
|
+
print(f" {G}✓{R} Vault: {vault_path}")
|
|
99
|
+
print(f" {G}✓{R} Config: {CONFIG_FILE}")
|
|
100
|
+
print()
|
|
101
|
+
print(f" {Y}Next step:{R} Connect to Claude Code:")
|
|
102
|
+
print(f" {Y}kyp-mem setup-claude{R}")
|
|
103
|
+
print()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _run_setup_claude(global_config: bool = False):
|
|
107
|
+
from .config import get_vault_path
|
|
108
|
+
|
|
109
|
+
vault_path = get_vault_path()
|
|
110
|
+
kyp_mem_bin = shutil.which("kyp-mem")
|
|
111
|
+
|
|
112
|
+
if not kyp_mem_bin:
|
|
113
|
+
print(f" {Y}Warning:{R} 'kyp-mem' not found in PATH.")
|
|
114
|
+
print(f" {D}Make sure you installed with: pip install kyp-mem{R}")
|
|
115
|
+
print()
|
|
116
|
+
kyp_mem_bin = "kyp-mem"
|
|
117
|
+
|
|
118
|
+
if global_config:
|
|
119
|
+
settings_path = Path.home() / ".claude" / "settings.json"
|
|
120
|
+
scope = "global"
|
|
121
|
+
else:
|
|
122
|
+
settings_path = Path.cwd() / ".claude" / "settings.json"
|
|
123
|
+
scope = "project"
|
|
124
|
+
|
|
125
|
+
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
|
|
127
|
+
settings = {}
|
|
128
|
+
if settings_path.exists():
|
|
129
|
+
try:
|
|
130
|
+
settings = json.loads(settings_path.read_text())
|
|
131
|
+
except json.JSONDecodeError:
|
|
132
|
+
settings = {}
|
|
133
|
+
|
|
134
|
+
mcp_servers = settings.setdefault("mcpServers", {})
|
|
135
|
+
|
|
136
|
+
mcp_servers["kyp-mem"] = {
|
|
137
|
+
"command": kyp_mem_bin,
|
|
138
|
+
"args": ["serve"],
|
|
139
|
+
"env": {
|
|
140
|
+
"KYP_VAULT": vault_path,
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
|
|
145
|
+
|
|
146
|
+
print()
|
|
147
|
+
print(f" {C}KYP-MEM{R} — Claude Code Setup")
|
|
148
|
+
print()
|
|
149
|
+
print(f" {G}✓{R} MCP server added to {scope} settings")
|
|
150
|
+
print(f" {D} File: {settings_path}{R}")
|
|
151
|
+
print(f" {D} Command: {kyp_mem_bin} serve{R}")
|
|
152
|
+
print(f" {D} Vault: {vault_path}{R}")
|
|
153
|
+
print()
|
|
154
|
+
print(f" {C}Done!{R} Restart Claude Code and kyp-mem will run automatically.")
|
|
155
|
+
print(f" Claude gets these tools: kyp_list, kyp_read, kyp_write, kyp_delete,")
|
|
156
|
+
print(f" kyp_search, kyp_tags, kyp_related, kyp_recent, kyp_stats")
|
|
157
|
+
print()
|
|
158
|
+
print(f" {D}To open the web UI anytime:{R} {Y}kyp-mem ui{R}")
|
|
159
|
+
print()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _run_stats():
|
|
163
|
+
from .config import get_vault_path
|
|
164
|
+
from .vault import Vault
|
|
165
|
+
vault_path = get_vault_path()
|
|
166
|
+
v = Vault(vault_path)
|
|
167
|
+
s = v.get_stats()
|
|
168
|
+
print(f"{C}KYP-MEM{R} vault: {vault_path}")
|
|
169
|
+
print(f" Notes: {G}{s['notes']}{R}")
|
|
170
|
+
print(f" Folders: {G}{s['folders']}{R}")
|
|
171
|
+
print(f" Tags: {G}{s['tags']}{R}")
|
|
172
|
+
print(f" Links: {G}{s['links']}{R}")
|
|
173
|
+
print(f" Backlinks: {G}{s['backlinks']}{R}")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _run_tree():
|
|
177
|
+
from .config import get_vault_path
|
|
178
|
+
from .vault import Vault
|
|
179
|
+
vault_path = get_vault_path()
|
|
180
|
+
v = Vault(vault_path)
|
|
181
|
+
print(f"{C}KYP-MEM{R} vault: {vault_path}\n")
|
|
182
|
+
_print_tree(v.get_full_tree(), "")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _run_doctor():
|
|
186
|
+
from .config import CONFIG_FILE, load_config, get_vault_path
|
|
187
|
+
|
|
188
|
+
print()
|
|
189
|
+
print(f" {C}KYP-MEM{R} — Health Check")
|
|
190
|
+
print()
|
|
191
|
+
|
|
192
|
+
# Config
|
|
193
|
+
if CONFIG_FILE.exists():
|
|
194
|
+
print(f" {G}✓{R} Config exists: {CONFIG_FILE}")
|
|
195
|
+
else:
|
|
196
|
+
print(f" {Y}✗{R} No config file. Run: kyp-mem init")
|
|
197
|
+
|
|
198
|
+
# Vault
|
|
199
|
+
vault_path = get_vault_path()
|
|
200
|
+
vault_dir = Path(vault_path)
|
|
201
|
+
if vault_dir.exists():
|
|
202
|
+
md_count = len(list(vault_dir.rglob("*.md")))
|
|
203
|
+
print(f" {G}✓{R} Vault exists: {vault_path} ({md_count} notes)")
|
|
204
|
+
else:
|
|
205
|
+
print(f" {Y}✗{R} Vault not found: {vault_path}")
|
|
206
|
+
|
|
207
|
+
# Claude Code config
|
|
208
|
+
for label, path in [
|
|
209
|
+
("project", Path.cwd() / ".claude" / "settings.json"),
|
|
210
|
+
("global", Path.home() / ".claude" / "settings.json"),
|
|
211
|
+
]:
|
|
212
|
+
if path.exists():
|
|
213
|
+
try:
|
|
214
|
+
s = json.loads(path.read_text())
|
|
215
|
+
if "kyp-mem" in s.get("mcpServers", {}):
|
|
216
|
+
print(f" {G}✓{R} Claude Code ({label}): kyp-mem configured")
|
|
217
|
+
else:
|
|
218
|
+
print(f" {D}·{R} Claude Code ({label}): exists but kyp-mem not configured")
|
|
219
|
+
except json.JSONDecodeError:
|
|
220
|
+
print(f" {Y}✗{R} Claude Code ({label}): invalid JSON")
|
|
221
|
+
else:
|
|
222
|
+
print(f" {D}·{R} Claude Code ({label}): no settings file")
|
|
223
|
+
|
|
224
|
+
# Binary
|
|
225
|
+
kyp_bin = shutil.which("kyp-mem")
|
|
226
|
+
if kyp_bin:
|
|
227
|
+
print(f" {G}✓{R} Binary in PATH: {kyp_bin}")
|
|
228
|
+
else:
|
|
229
|
+
print(f" {Y}✗{R} 'kyp-mem' not found in PATH")
|
|
230
|
+
|
|
231
|
+
# MCP dependency
|
|
232
|
+
try:
|
|
233
|
+
import mcp
|
|
234
|
+
print(f" {G}✓{R} MCP SDK installed: {mcp.__version__}")
|
|
235
|
+
except ImportError:
|
|
236
|
+
print(f" {Y}✗{R} MCP SDK not installed")
|
|
237
|
+
except AttributeError:
|
|
238
|
+
print(f" {G}✓{R} MCP SDK installed")
|
|
239
|
+
|
|
240
|
+
print()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _print_banner():
|
|
244
|
+
print()
|
|
245
|
+
print(f"{C} KYP-MEM{R} — Know Your Project Memory")
|
|
246
|
+
print(f" {D}Headless Obsidian for AI agents{R}")
|
|
247
|
+
print()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _print_tree(node: dict, prefix: str):
|
|
251
|
+
if node["type"] == "folder":
|
|
252
|
+
if node["name"] != "vault":
|
|
253
|
+
print(f"{prefix}{C}{node['name']}/{R}")
|
|
254
|
+
prefix += " "
|
|
255
|
+
for child in node.get("children", []):
|
|
256
|
+
_print_tree(child, prefix)
|
|
257
|
+
else:
|
|
258
|
+
name = node["name"].replace(".md", "")
|
|
259
|
+
print(f"{prefix}{name}")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
if __name__ == "__main__":
|
|
263
|
+
main()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""KYP-MEM configuration — stored at ~/.kyp-mem/config.json"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
CONFIG_DIR = Path.home() / ".kyp-mem"
|
|
8
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
9
|
+
DEFAULT_VAULT = str(CONFIG_DIR / "vault")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_config() -> dict:
|
|
13
|
+
if CONFIG_FILE.exists():
|
|
14
|
+
try:
|
|
15
|
+
return json.loads(CONFIG_FILE.read_text())
|
|
16
|
+
except json.JSONDecodeError:
|
|
17
|
+
return {"vault_path": DEFAULT_VAULT}
|
|
18
|
+
return {"vault_path": DEFAULT_VAULT}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def save_config(config: dict):
|
|
22
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
CONFIG_FILE.write_text(json.dumps(config, indent=2) + "\n")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_vault_path() -> str:
|
|
27
|
+
env = os.environ.get("KYP_VAULT")
|
|
28
|
+
if env:
|
|
29
|
+
return env
|
|
30
|
+
config = load_config()
|
|
31
|
+
return config.get("vault_path", DEFAULT_VAULT)
|