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 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);
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ """KYP-MEM — Know Your Project Memory. Headless Obsidian for AI agents."""
2
+
3
+ __version__ = "0.2.0"
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)