lybrary 0.1.0__tar.gz

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.
@@ -0,0 +1,47 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.egg
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .eggs/
11
+ wheels/
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ env/
17
+ ENV/
18
+
19
+ # Testing & coverage
20
+ .pytest_cache/
21
+ .coverage
22
+ .coverage.*
23
+ htmlcov/
24
+ *.xml
25
+
26
+ # Type checking
27
+ .mypy_cache/
28
+ .pyright/
29
+
30
+ # lybrary runtime data — never commit a repo's index
31
+ .lybrary/
32
+
33
+ # IDE
34
+ .idea/
35
+ .vscode/
36
+ *.swp
37
+ *.swo
38
+ *.DS_Store
39
+
40
+ # Distribution / packaging
41
+ MANIFEST
42
+ pip-wheel-metadata/
43
+ share/python-wheels/
44
+ *.dist-info/
45
+
46
+ # Logs
47
+ *.log
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to lybrary are documented here.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ---
7
+
8
+ ## [0.1.0] — 2026-08-07
9
+
10
+ ### Added
11
+ - AST-aware chunking (cAST-style) via tree-sitter for Python, JavaScript,
12
+ TypeScript, TSX, Go, Rust, Java, C, and C++.
13
+ - Incremental indexing via xxhash content-hash tracking (`file_hashes.json`).
14
+ - LanceDB-backed vector store with MiniLM-L6-v2 embeddings
15
+ (sentence-transformers, fully local).
16
+ - Background daemon with watchdog file watcher and configurable debounce.
17
+ - CLI commands: `init`, `start`, `stop`, `status`, `index`, `query`, `logs`,
18
+ `mcp`, `version`.
19
+ - MCP server (`lybrary mcp`) exposing three tools over stdio transport:
20
+ - `memory_query` — semantic search over the indexed code memory.
21
+ - `memory_status` — daemon/index health check.
22
+ - `memory_update` — incremental or full re-index, optionally scoped to
23
+ specific files.
24
+ - `py.typed` marker for PEP 561 compliance.
25
+
26
+ ### Fixed
27
+ - TSX files now use the tsx tree-sitter grammar instead of being silently
28
+ downgraded to the TypeScript grammar.
29
+ - Nested definitions (methods inside classes) are no longer emitted twice.
30
+ - Overlapping byte ranges are properly merged before computing leftover
31
+ module-level content.
32
+ - Chunk IDs include the byte offset, preventing collisions for same-named
33
+ symbols at the same line.
34
+ - `_sanitize_name` strips characters that would corrupt chunk IDs or
35
+ LanceDB filter strings.
36
+ - `delete_path` escapes double-quotes in paths to prevent filter injection.
37
+ - `search_vector` no longer returns the raw 384-float vector column to callers.
38
+ - Full re-index uses `truncate()` (drop + recreate) instead of iterating
39
+ `all_paths()`, eliminating stale row leakage.
40
+ - File-move/rename events are handled correctly: the new path is indexed and
41
+ the old path is deleted from the store.
42
+ - `_update_file` reads the file exactly once, eliminating the race condition
43
+ between chunk generation and hash storage.
44
+ - Windows daemon now spawns a detached subprocess and writes a PID file so
45
+ that `lybrary stop` and `lybrary status` work on Windows.
46
+ - Score formula uses proper cosine similarity (`1 - d²/2`) for unit-normalised
47
+ vectors instead of the previous `1/(1+d)` approximation.
48
+ - Unused `ThreadPoolExecutor` import removed from `walker.py`.
49
+ - Dead `merge_siblings` config option removed from `ChunkConfig`.
50
+ - Embedder normalises the device string before using it as an `lru_cache` key.
lybrary-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: lybrary
3
+ Version: 0.1.0
4
+ Summary: Living structure-aware code memory for AI coding agents
5
+ Author: lybrary contributors
6
+ License-Expression: MIT
7
+ Keywords: agent,ast,code,indexing,mcp,memory,rag
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: fastembed<2.0.0,>=0.4.0
19
+ Requires-Dist: mcp<2.0.0,>=1.0.0
20
+ Requires-Dist: numpy<3.0.0,>=1.26.0
21
+ Requires-Dist: pathspec<1.0.0,>=0.12.0
22
+ Requires-Dist: pydantic-settings<3.0.0,>=2.2.0
23
+ Requires-Dist: pydantic<3.0.0,>=2.6.0
24
+ Requires-Dist: rich<14.0.0,>=13.7.0
25
+ Requires-Dist: tomli-w<2.0.0,>=1.0.0
26
+ Requires-Dist: tree-sitter-c<1.0.0,>=0.21.0
27
+ Requires-Dist: tree-sitter-cpp<1.0.0,>=0.22.0
28
+ Requires-Dist: tree-sitter-go<1.0.0,>=0.21.0
29
+ Requires-Dist: tree-sitter-java<1.0.0,>=0.21.0
30
+ Requires-Dist: tree-sitter-javascript<1.0.0,>=0.21.0
31
+ Requires-Dist: tree-sitter-python<1.0.0,>=0.21.0
32
+ Requires-Dist: tree-sitter-rust<1.0.0,>=0.21.0
33
+ Requires-Dist: tree-sitter-typescript<1.0.0,>=0.21.0
34
+ Requires-Dist: tree-sitter<0.26.0,>=0.24.0
35
+ Requires-Dist: typer[all]<1.0.0,>=0.12.0
36
+ Requires-Dist: watchdog<6.0.0,>=4.0.0
37
+ Requires-Dist: xxhash<4.0.0,>=3.4.0
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest-asyncio<1.0.0,>=0.23.0; extra == 'dev'
40
+ Requires-Dist: pytest<9.0.0,>=8.0.0; extra == 'dev'
41
+ Requires-Dist: ruff<1.0.0,>=0.4.0; extra == 'dev'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # lybrary
45
+
46
+ **Living structure-aware code memory for AI coding agents.**
47
+
48
+ `lybrary` indexes your repository with real AST boundaries (functions, classes, methods), keeps the index fresh via a background daemon, and gives AI agents a high-signal memory they can query instead of reading raw files.
49
+
50
+ This reduces token burn and hallucinations compared to pure agentic search or flat embedding RAG.
51
+
52
+ ## Why lybrary?
53
+
54
+ Current IDE agents either:
55
+
56
+ - Re-explore the repo with grep/read every session (expensive, amnesiac), or
57
+ - Use flat semantic search that doesn't understand code structure.
58
+
59
+ `lybrary` sits in the middle:
60
+
61
+ - **AST-aware chunks** (cAST-style) — never splits a function in half
62
+ - **Persistent daemon** that auto-updates on every file change
63
+ - **Hybrid retrieval** (vector search + token-budget packing)
64
+ - **Agent-first design** — agents query memory instead of walking the tree
65
+ - **MCP server** — works natively with Kiro, Cursor, Claude Desktop, Windsurf, and any MCP-compatible IDE
66
+ - **Fully local** — no cloud, no API keys, embeddings run on your machine
67
+
68
+ ## Quick start
69
+
70
+ ```bash
71
+ pip install lybrary
72
+
73
+ cd /path/to/your/repo
74
+ lybrary init
75
+ lybrary start # builds index + starts background daemon
76
+ lybrary query "authentication flow"
77
+ ```
78
+
79
+ After `lybrary start`, the daemon keeps running even if you close the terminal. File changes are picked up automatically and only the affected chunks are re-indexed.
80
+
81
+ ## MCP integration (AI IDEs)
82
+
83
+ Add this to your MCP config (works with Kiro, Cursor, Claude Desktop, Windsurf):
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "lybrary": {
89
+ "command": "lybrary",
90
+ "args": ["mcp"]
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ The agent then has three tools available:
97
+
98
+ | Tool | What it does |
99
+ |---|---|
100
+ | `memory_query` | Semantic search — returns ranked code chunks with full source, file path, and line numbers |
101
+ | `memory_status` | Reports daemon state, chunk count, and tracked files |
102
+ | `memory_update` | Triggers incremental or full re-index, optionally scoped to specific files |
103
+
104
+ **Agents should call `memory_query` before reading any files.** This replaces multi-file reads with a single targeted query, cutting token usage by 80–90% on large codebases.
105
+
106
+ ## CLI
107
+
108
+ | Command | Description |
109
+ |--------------------|--------------------------------------------------|
110
+ | `lybrary init` | Create `.lybrary/` and default config |
111
+ | `lybrary start` | Index (if needed) + start persistent daemon |
112
+ | `lybrary stop` | Stop the daemon |
113
+ | `lybrary status` | Show running state, chunk count, tracked files |
114
+ | `lybrary index` | Force (re)index |
115
+ | `lybrary query` | Semantic search over the memory |
116
+ | `lybrary logs` | View / follow daemon log |
117
+ | `lybrary mcp` | Start MCP server (stdio transport) |
118
+
119
+ ## How chunking works
120
+
121
+ 1. Detect language from file extension
122
+ 2. Parse with **tree-sitter**
123
+ 3. Extract definition nodes (functions, classes, methods, interfaces, impls…)
124
+ 4. Container nodes (classes, impls) emit as a chunk **and** recurse so nested methods get their own chunks
125
+ 5. Attach rich metadata + a context header used for embedding
126
+ 6. Fall back to careful line-based splitting only when a grammar is missing
127
+
128
+ Supported languages: Python, JavaScript, TypeScript, TSX, Go, Rust, Java, C, C++.
129
+
130
+ ## Architecture (v0.1)
131
+
132
+ ```
133
+ .lybrary/
134
+ ├── config.toml
135
+ ├── index.db # SQLite: chunks + vectors (numpy float32 blobs)
136
+ ├── file_hashes.json # content-hash map for incremental updates
137
+ ├── daemon.pid
138
+ └── daemon.log
139
+ ```
140
+
141
+ - **Indexer**: tree-sitter → AST chunks → local embeddings (fastembed / all-MiniLM-L6-v2, ONNX Runtime)
142
+ - **Store**: SQLite + numpy (cosine similarity via batched dot product)
143
+ - **Daemon**: watchdog file watcher + debounce + incremental re-chunk/embed
144
+ - **Query**: vector search + token-budget packing
145
+ - **MCP**: FastMCP server over stdio
146
+
147
+ ## Roadmap
148
+
149
+ - [x] AST chunker (multi-language, cAST-style)
150
+ - [x] Incremental indexing via content hashes
151
+ - [x] Background daemon + file watcher (Windows + Unix)
152
+ - [x] CLI (`init/start/stop/status/index/query/logs/mcp`)
153
+ - [x] MCP server (`memory_query`, `memory_status`, `memory_update`)
154
+ - [ ] Call/import graph + expansion
155
+ - [ ] Hierarchical file/package summaries
156
+ - [ ] Cross-session decision memory
157
+ - [ ] systemd/launchd user service helper
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1,118 @@
1
+ # lybrary
2
+
3
+ **Living structure-aware code memory for AI coding agents.**
4
+
5
+ `lybrary` indexes your repository with real AST boundaries (functions, classes, methods), keeps the index fresh via a background daemon, and gives AI agents a high-signal memory they can query instead of reading raw files.
6
+
7
+ This reduces token burn and hallucinations compared to pure agentic search or flat embedding RAG.
8
+
9
+ ## Why lybrary?
10
+
11
+ Current IDE agents either:
12
+
13
+ - Re-explore the repo with grep/read every session (expensive, amnesiac), or
14
+ - Use flat semantic search that doesn't understand code structure.
15
+
16
+ `lybrary` sits in the middle:
17
+
18
+ - **AST-aware chunks** (cAST-style) — never splits a function in half
19
+ - **Persistent daemon** that auto-updates on every file change
20
+ - **Hybrid retrieval** (vector search + token-budget packing)
21
+ - **Agent-first design** — agents query memory instead of walking the tree
22
+ - **MCP server** — works natively with Kiro, Cursor, Claude Desktop, Windsurf, and any MCP-compatible IDE
23
+ - **Fully local** — no cloud, no API keys, embeddings run on your machine
24
+
25
+ ## Quick start
26
+
27
+ ```bash
28
+ pip install lybrary
29
+
30
+ cd /path/to/your/repo
31
+ lybrary init
32
+ lybrary start # builds index + starts background daemon
33
+ lybrary query "authentication flow"
34
+ ```
35
+
36
+ After `lybrary start`, the daemon keeps running even if you close the terminal. File changes are picked up automatically and only the affected chunks are re-indexed.
37
+
38
+ ## MCP integration (AI IDEs)
39
+
40
+ Add this to your MCP config (works with Kiro, Cursor, Claude Desktop, Windsurf):
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "lybrary": {
46
+ "command": "lybrary",
47
+ "args": ["mcp"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ The agent then has three tools available:
54
+
55
+ | Tool | What it does |
56
+ |---|---|
57
+ | `memory_query` | Semantic search — returns ranked code chunks with full source, file path, and line numbers |
58
+ | `memory_status` | Reports daemon state, chunk count, and tracked files |
59
+ | `memory_update` | Triggers incremental or full re-index, optionally scoped to specific files |
60
+
61
+ **Agents should call `memory_query` before reading any files.** This replaces multi-file reads with a single targeted query, cutting token usage by 80–90% on large codebases.
62
+
63
+ ## CLI
64
+
65
+ | Command | Description |
66
+ |--------------------|--------------------------------------------------|
67
+ | `lybrary init` | Create `.lybrary/` and default config |
68
+ | `lybrary start` | Index (if needed) + start persistent daemon |
69
+ | `lybrary stop` | Stop the daemon |
70
+ | `lybrary status` | Show running state, chunk count, tracked files |
71
+ | `lybrary index` | Force (re)index |
72
+ | `lybrary query` | Semantic search over the memory |
73
+ | `lybrary logs` | View / follow daemon log |
74
+ | `lybrary mcp` | Start MCP server (stdio transport) |
75
+
76
+ ## How chunking works
77
+
78
+ 1. Detect language from file extension
79
+ 2. Parse with **tree-sitter**
80
+ 3. Extract definition nodes (functions, classes, methods, interfaces, impls…)
81
+ 4. Container nodes (classes, impls) emit as a chunk **and** recurse so nested methods get their own chunks
82
+ 5. Attach rich metadata + a context header used for embedding
83
+ 6. Fall back to careful line-based splitting only when a grammar is missing
84
+
85
+ Supported languages: Python, JavaScript, TypeScript, TSX, Go, Rust, Java, C, C++.
86
+
87
+ ## Architecture (v0.1)
88
+
89
+ ```
90
+ .lybrary/
91
+ ├── config.toml
92
+ ├── index.db # SQLite: chunks + vectors (numpy float32 blobs)
93
+ ├── file_hashes.json # content-hash map for incremental updates
94
+ ├── daemon.pid
95
+ └── daemon.log
96
+ ```
97
+
98
+ - **Indexer**: tree-sitter → AST chunks → local embeddings (fastembed / all-MiniLM-L6-v2, ONNX Runtime)
99
+ - **Store**: SQLite + numpy (cosine similarity via batched dot product)
100
+ - **Daemon**: watchdog file watcher + debounce + incremental re-chunk/embed
101
+ - **Query**: vector search + token-budget packing
102
+ - **MCP**: FastMCP server over stdio
103
+
104
+ ## Roadmap
105
+
106
+ - [x] AST chunker (multi-language, cAST-style)
107
+ - [x] Incremental indexing via content hashes
108
+ - [x] Background daemon + file watcher (Windows + Unix)
109
+ - [x] CLI (`init/start/stop/status/index/query/logs/mcp`)
110
+ - [x] MCP server (`memory_query`, `memory_status`, `memory_update`)
111
+ - [ ] Call/import graph + expansion
112
+ - [ ] Hierarchical file/package summaries
113
+ - [ ] Cross-session decision memory
114
+ - [ ] systemd/launchd user service helper
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,3 @@
1
+ """lybrary — Living structure-aware code memory for AI coding agents."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,39 @@
1
+ """Entry point for the Windows background daemon subprocess.
2
+
3
+ Invoked as: python -m lybrary._daemon_worker <root>
4
+
5
+ Writes its own PID to the pid file (overwriting the one set by the parent
6
+ just before spawn, in case they differ), then starts the file watcher.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ def main() -> None:
17
+ if len(sys.argv) < 2:
18
+ print("Usage: python -m lybrary._daemon_worker <repo_root>", file=sys.stderr)
19
+ sys.exit(1)
20
+
21
+ root = Path(sys.argv[1]).resolve()
22
+
23
+ from lybrary.config import LybraryConfig, get_lybrary_dir
24
+
25
+ pid_file = get_lybrary_dir(root) / "daemon.pid"
26
+ # Overwrite with our actual PID (the parent wrote its own PID first).
27
+ pid_file.write_text(str(os.getpid()))
28
+
29
+ config = LybraryConfig.load(root)
30
+
31
+ print(f"lybrary daemon watching {root}", flush=True)
32
+
33
+ from lybrary.watcher import start_watching
34
+
35
+ start_watching(root, config)
36
+
37
+
38
+ if __name__ == "__main__":
39
+ main()
@@ -0,0 +1,247 @@
1
+ """lybrary CLI — living code memory for AI agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import typer
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+
13
+ from lybrary import __version__
14
+ from lybrary.config import LybraryConfig, find_repo_root, get_lybrary_dir
15
+
16
+ app = typer.Typer(
17
+ name="lybrary",
18
+ help="Living structure-aware code memory for AI coding agents.",
19
+ no_args_is_help=True,
20
+ rich_markup_mode="rich",
21
+ )
22
+ console = Console()
23
+
24
+
25
+ def _root(path: Optional[Path] = None) -> Path:
26
+ return find_repo_root(path)
27
+
28
+
29
+ @app.command()
30
+ def init(
31
+ path: Optional[Path] = typer.Argument(None, help="Repository path (default: cwd)"),
32
+ ) -> None:
33
+ """Initialize lybrary in a repository."""
34
+ root = _root(path)
35
+ ly_dir = get_lybrary_dir(root)
36
+ ly_dir.mkdir(parents=True, exist_ok=True)
37
+
38
+ config = LybraryConfig()
39
+ config.save(root)
40
+
41
+ # Create a basic ignore file if missing
42
+ ignore = ly_dir / "ignore"
43
+ if not ignore.exists():
44
+ ignore.write_text(
45
+ "\n".join(
46
+ [
47
+ "# Extra ignore patterns (gitignore syntax)",
48
+ "*.min.js",
49
+ "*.map",
50
+ "dist/",
51
+ "build/",
52
+ "",
53
+ ]
54
+ )
55
+ )
56
+
57
+ console.print(
58
+ Panel.fit(
59
+ f"[bold green]lybrary initialized[/bold green]\n\n"
60
+ f"Root: [cyan]{root}[/cyan]\n"
61
+ f"Data: [cyan]{ly_dir}[/cyan]\n\n"
62
+ f"Next: [bold]lybrary start[/bold]",
63
+ title="lybrary",
64
+ )
65
+ )
66
+
67
+
68
+ @app.command()
69
+ def start(
70
+ path: Optional[Path] = typer.Argument(None, help="Repository path"),
71
+ foreground: bool = typer.Option(False, "--foreground", "-f", help="Run in foreground"),
72
+ ) -> None:
73
+ """Start the lybrary daemon (indexes if needed, then watches for changes)."""
74
+ root = _root(path)
75
+ ly_dir = get_lybrary_dir(root)
76
+
77
+ if not ly_dir.exists():
78
+ console.print("[yellow]No .lybrary/ found. Running init first...[/yellow]")
79
+ init(path)
80
+
81
+ from lybrary.daemon import start_daemon
82
+
83
+ start_daemon(root, foreground=foreground)
84
+
85
+
86
+ @app.command()
87
+ def stop(
88
+ path: Optional[Path] = typer.Argument(None, help="Repository path"),
89
+ ) -> None:
90
+ """Stop the running lybrary daemon."""
91
+ root = _root(path)
92
+ from lybrary.daemon import stop_daemon
93
+
94
+ stop_daemon(root)
95
+
96
+
97
+ @app.command()
98
+ def status(
99
+ path: Optional[Path] = typer.Argument(None, help="Repository path"),
100
+ ) -> None:
101
+ """Show daemon and index status."""
102
+ root = _root(path)
103
+ from lybrary.daemon import daemon_status
104
+
105
+ info = daemon_status(root)
106
+
107
+ table = Table(title="lybrary status", show_header=False)
108
+ table.add_column("Key", style="bold")
109
+ table.add_column("Value")
110
+
111
+ for k, v in info.items():
112
+ table.add_row(k, str(v))
113
+
114
+ console.print(table)
115
+
116
+
117
+ @app.command()
118
+ def index(
119
+ path: Optional[Path] = typer.Argument(None, help="Repository path"),
120
+ full: bool = typer.Option(False, "--full", help="Force full re-index"),
121
+ ) -> None:
122
+ """Index (or re-index) the repository."""
123
+ root = _root(path)
124
+ ly_dir = get_lybrary_dir(root)
125
+ if not ly_dir.exists():
126
+ init(path)
127
+
128
+ from lybrary.indexer.store import IndexStore
129
+ from lybrary.indexer.walker import index_repository
130
+
131
+ config = LybraryConfig.load(root)
132
+ store = IndexStore(root)
133
+
134
+ console.print(f"[bold]Indexing[/bold] [cyan]{root}[/cyan] ...")
135
+ stats = index_repository(root, store, config, full=full)
136
+
137
+ console.print(
138
+ Panel.fit(
139
+ f"[green]Index complete[/green]\n\n"
140
+ f"Files: {stats.get('files', 0)}\n"
141
+ f"Chunks: {stats.get('chunks', 0)}\n"
142
+ f"Added/updated: {stats.get('updated', 0)}",
143
+ title="lybrary index",
144
+ )
145
+ )
146
+
147
+
148
+ @app.command()
149
+ def query(
150
+ q: str = typer.Argument(..., help="Search query"),
151
+ path: Optional[Path] = typer.Option(None, "--path", "-p", help="Repository path"),
152
+ limit: int = typer.Option(8, "--limit", "-n", help="Max results"),
153
+ max_tokens: int = typer.Option(6000, "--max-tokens", help="Token budget for context"),
154
+ ) -> None:
155
+ """Search the code memory."""
156
+ root = _root(path)
157
+ from lybrary.query import search
158
+
159
+ results = search(root, q, limit=limit, max_tokens=max_tokens)
160
+
161
+ if not results:
162
+ console.print("[yellow]No results.[/yellow]")
163
+ return
164
+
165
+ for i, r in enumerate(results, 1):
166
+ console.print(
167
+ Panel(
168
+ f"[dim]{r.get('path')} :: {r.get('symbol', '')}[/dim]\n\n"
169
+ f"{r.get('content', '')[:800]}{'...' if len(r.get('content', '')) > 800 else ''}",
170
+ title=f"[{i}] {r.get('score', 0):.3f}",
171
+ border_style="blue",
172
+ )
173
+ )
174
+
175
+
176
+ @app.command()
177
+ def logs(
178
+ path: Optional[Path] = typer.Argument(None, help="Repository path"),
179
+ follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"),
180
+ ) -> None:
181
+ """Show daemon logs."""
182
+ root = _root(path)
183
+ log_file = get_lybrary_dir(root) / "daemon.log"
184
+ if not log_file.exists():
185
+ console.print("[yellow]No log file yet.[/yellow]")
186
+ raise typer.Exit(1)
187
+
188
+ if follow:
189
+ import time
190
+
191
+ with open(log_file) as f:
192
+ f.seek(0, 2)
193
+ while True:
194
+ line = f.readline()
195
+ if line:
196
+ console.print(line.rstrip())
197
+ else:
198
+ time.sleep(0.3)
199
+ else:
200
+ console.print(log_file.read_text()[-8000:])
201
+
202
+
203
+ @app.command()
204
+ def mcp(
205
+ repo_root: Optional[Path] = typer.Option(
206
+ None, "--root", "-r", help="Repository root (default: auto-detect)"
207
+ ),
208
+ ) -> None:
209
+ """Start the MCP server (stdio transport) for AI IDE integration.
210
+
211
+ Configure your IDE / MCP host to run: lybrary mcp
212
+
213
+ Kiro / Cursor / Claude Desktop example config:
214
+ {
215
+ "mcpServers": {
216
+ "lybrary": {
217
+ "command": "lybrary",
218
+ "args": ["mcp"]
219
+ }
220
+ }
221
+ }
222
+ """
223
+ try:
224
+ from lybrary.mcp.server import mcp as _mcp_server # noqa: F401
225
+ except ImportError:
226
+ console.print(
227
+ "[red]MCP dependencies not installed.[/red]\n"
228
+ "Run: [bold]pip install 'lybrary[mcp]'[/bold]"
229
+ )
230
+ raise typer.Exit(1)
231
+
232
+ if repo_root:
233
+ import os
234
+ os.chdir(repo_root)
235
+
236
+ from lybrary.mcp.server import run
237
+ run()
238
+
239
+
240
+ @app.command()
241
+ def version() -> None:
242
+ """Show version."""
243
+ console.print(f"lybrary {__version__}")
244
+
245
+
246
+ if __name__ == "__main__":
247
+ app()