knowledge-rag 3.3.0__tar.gz → 3.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: knowledge-rag
3
- Version: 3.3.0
3
+ Version: 3.3.1
4
4
  Summary: Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 12 MCP Tools. Zero external servers.
5
5
  Project-URL: Homepage, https://github.com/lyonzin/knowledge-rag
6
6
  Project-URL: Repository, https://github.com/lyonzin/knowledge-rag
@@ -38,7 +38,7 @@ Description-Content-Type: text/markdown
38
38
 
39
39
  <div align="center">
40
40
 
41
- ![Version](https://img.shields.io/badge/version-3.3.0-blue.svg)
41
+ ![Version](https://img.shields.io/badge/version-3.3.1-blue.svg)
42
42
  ![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12-green.svg)
43
43
  ![License](https://img.shields.io/badge/license-MIT-yellow.svg)
44
44
  ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)
@@ -408,21 +408,23 @@ flowchart LR
408
408
 
409
409
  ### Quick Start (3 steps)
410
410
 
411
- **Step 1: Clone and install**
411
+ **Step 1: Install**
412
412
 
413
413
  ```bash
414
- # Clone to your home directory
414
+ # Option A: pip install (recommended)
415
+ pip install knowledge-rag
416
+ knowledge-rag init # Exports config template, presets, creates documents/
417
+
418
+ # Option B: Clone from source
415
419
  git clone https://github.com/lyonzin/knowledge-rag.git ~/knowledge-rag
416
420
  cd ~/knowledge-rag
417
-
418
- # Create virtual environment and install
419
421
  python3 -m venv venv
420
422
  source venv/bin/activate # Linux/macOS
421
423
  # .\venv\Scripts\activate # Windows
422
424
  pip install -r requirements.txt
423
425
  ```
424
426
 
425
- > **Windows users**: `~/knowledge-rag` becomes `C:\Users\YourName\knowledge-rag`. Use `python` instead of `python3`.
427
+ > **Windows users**: Use `python` instead of `python3`.
426
428
 
427
429
  **Step 2: Configure Claude Code**
428
430
 
@@ -1193,6 +1195,12 @@ With ~200 documents, expect ~300-500MB RAM. The embedding model (~50MB) and rera
1193
1195
 
1194
1196
  ## Changelog
1195
1197
 
1198
+ ### v3.3.1 (2026-04-06)
1199
+
1200
+ - **FIX**: YAML null values (`category_mappings:` without value) no longer crash the server — falls back to defaults
1201
+ - **FIX**: Presets and config template now bundled in pip wheel (`knowledge-rag init` exports them)
1202
+ - **NEW**: `knowledge-rag init` CLI command — exports config template, presets, and creates documents/ in current directory
1203
+
1196
1204
  ### v3.3.0 (2026-04-05)
1197
1205
 
1198
1206
  - **NEW**: YAML configuration system — fully customizable via `config.yaml`
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- ![Version](https://img.shields.io/badge/version-3.3.0-blue.svg)
5
+ ![Version](https://img.shields.io/badge/version-3.3.1-blue.svg)
6
6
  ![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12-green.svg)
7
7
  ![License](https://img.shields.io/badge/license-MIT-yellow.svg)
8
8
  ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)
@@ -372,21 +372,23 @@ flowchart LR
372
372
 
373
373
  ### Quick Start (3 steps)
374
374
 
375
- **Step 1: Clone and install**
375
+ **Step 1: Install**
376
376
 
377
377
  ```bash
378
- # Clone to your home directory
378
+ # Option A: pip install (recommended)
379
+ pip install knowledge-rag
380
+ knowledge-rag init # Exports config template, presets, creates documents/
381
+
382
+ # Option B: Clone from source
379
383
  git clone https://github.com/lyonzin/knowledge-rag.git ~/knowledge-rag
380
384
  cd ~/knowledge-rag
381
-
382
- # Create virtual environment and install
383
385
  python3 -m venv venv
384
386
  source venv/bin/activate # Linux/macOS
385
387
  # .\venv\Scripts\activate # Windows
386
388
  pip install -r requirements.txt
387
389
  ```
388
390
 
389
- > **Windows users**: `~/knowledge-rag` becomes `C:\Users\YourName\knowledge-rag`. Use `python` instead of `python3`.
391
+ > **Windows users**: Use `python` instead of `python3`.
390
392
 
391
393
  **Step 2: Configure Claude Code**
392
394
 
@@ -1157,6 +1159,12 @@ With ~200 documents, expect ~300-500MB RAM. The embedding model (~50MB) and rera
1157
1159
 
1158
1160
  ## Changelog
1159
1161
 
1162
+ ### v3.3.1 (2026-04-06)
1163
+
1164
+ - **FIX**: YAML null values (`category_mappings:` without value) no longer crash the server — falls back to defaults
1165
+ - **FIX**: Presets and config template now bundled in pip wheel (`knowledge-rag init` exports them)
1166
+ - **NEW**: `knowledge-rag init` CLI command — exports config template, presets, and creates documents/ in current directory
1167
+
1160
1168
  ### v3.3.0 (2026-04-05)
1161
1169
 
1162
1170
  - **NEW**: YAML configuration system — fully customizable via `config.yaml`
@@ -44,6 +44,7 @@ else:
44
44
  # YAML CONFIG LOADER
45
45
  # ============================================================================
46
46
 
47
+
47
48
  def _load_yaml_config() -> dict:
48
49
  """Load config.yaml from BASE_DIR if it exists, otherwise return empty dict."""
49
50
  config_path = BASE_DIR / "config.yaml"
@@ -54,7 +55,7 @@ def _load_yaml_config() -> dict:
54
55
  with open(config_path, "r", encoding="utf-8") as f:
55
56
  data = yaml.safe_load(f)
56
57
  if not isinstance(data, dict):
57
- print(f"[WARN] config.yaml is not a valid mapping, ignoring")
58
+ print("[WARN] config.yaml is not a valid mapping, ignoring")
58
59
  return {}
59
60
  print(f"[INFO] Loaded config from {config_path}")
60
61
  return data
@@ -75,6 +76,16 @@ def _get(section: str, key: str, default):
75
76
  return val if val is not None else default
76
77
 
77
78
 
79
+ def _get_top(key: str, default):
80
+ """Get a top-level value from YAML, falling back to default if missing or None."""
81
+ val = _yaml.get(key)
82
+ if val is None:
83
+ return default
84
+ if not isinstance(val, type(default)):
85
+ return default
86
+ return val
87
+
88
+
78
89
  # ============================================================================
79
90
  # DEFAULTS (used when no config.yaml or field is omitted)
80
91
  # ============================================================================
@@ -92,44 +103,145 @@ _DEFAULT_CATEGORY_MAPPINGS = {
92
103
 
93
104
  _DEFAULT_KEYWORD_ROUTES = {
94
105
  "logscale": [
95
- "logscale", "lql", "cql", "humio", "crowdstrike query",
96
- "formattime", "groupby", "base64decode", "case{}", "regex",
106
+ "logscale",
107
+ "lql",
108
+ "cql",
109
+ "humio",
110
+ "crowdstrike query",
111
+ "formattime",
112
+ "groupby",
113
+ "base64decode",
114
+ "case{}",
115
+ "regex",
97
116
  ],
98
117
  "redteam": [
99
- "pentest", "exploit", "payload", "reverse shell", "privilege escalation",
100
- "lateral movement", "c2", "beacon", "cobalt strike", "metasploit",
101
- "gtfobins", "lolbas", "lolbin", "suid", "sudo", "byovd", "lol driver",
102
- "lolad", "lolapps", "hacktricks", "privesc", "kerberoast", "dcsync",
103
- "golden ticket", "pass-the-hash", "bloodhound", "mimikatz", "rubeus",
104
- "certipy", "adcs", "sqli", "xss", "ssti", "ssrf", "lfi", "rfi", "xxe",
105
- "deserialization", "ysoserial", "upload bypass", "reverse shell",
106
- "web shell", "hash cracking", "hashcat", "waf bypass", "amsi bypass",
107
- "uac bypass", "potato", "searchsploit", "exploit-db", "cve",
118
+ "pentest",
119
+ "exploit",
120
+ "payload",
121
+ "reverse shell",
122
+ "privilege escalation",
123
+ "lateral movement",
124
+ "c2",
125
+ "beacon",
126
+ "cobalt strike",
127
+ "metasploit",
128
+ "gtfobins",
129
+ "lolbas",
130
+ "lolbin",
131
+ "suid",
132
+ "sudo",
133
+ "byovd",
134
+ "lol driver",
135
+ "lolad",
136
+ "lolapps",
137
+ "hacktricks",
138
+ "privesc",
139
+ "kerberoast",
140
+ "dcsync",
141
+ "golden ticket",
142
+ "pass-the-hash",
143
+ "bloodhound",
144
+ "mimikatz",
145
+ "rubeus",
146
+ "certipy",
147
+ "adcs",
148
+ "sqli",
149
+ "xss",
150
+ "ssti",
151
+ "ssrf",
152
+ "lfi",
153
+ "rfi",
154
+ "xxe",
155
+ "deserialization",
156
+ "ysoserial",
157
+ "upload bypass",
158
+ "reverse shell",
159
+ "web shell",
160
+ "hash cracking",
161
+ "hashcat",
162
+ "waf bypass",
163
+ "amsi bypass",
164
+ "uac bypass",
165
+ "potato",
166
+ "searchsploit",
167
+ "exploit-db",
168
+ "cve",
108
169
  ],
109
170
  "blueteam": [
110
- "detection", "sigma", "yara", "ioc", "threat hunting",
111
- "incident response", "forensics", "malware analysis",
171
+ "detection",
172
+ "sigma",
173
+ "yara",
174
+ "ioc",
175
+ "threat hunting",
176
+ "incident response",
177
+ "forensics",
178
+ "malware analysis",
112
179
  ],
113
180
  "ctf": [
114
- "ctf", "flag", "hackthebox", "htb", "tryhackme",
115
- "picoctf", "writeup", "challenge",
181
+ "ctf",
182
+ "flag",
183
+ "hackthebox",
184
+ "htb",
185
+ "tryhackme",
186
+ "picoctf",
187
+ "writeup",
188
+ "challenge",
116
189
  ],
117
190
  "development": [
118
- "python", "typescript", "javascript", "api",
119
- "fastapi", "django", "react", "nodejs",
191
+ "python",
192
+ "typescript",
193
+ "javascript",
194
+ "api",
195
+ "fastapi",
196
+ "django",
197
+ "react",
198
+ "nodejs",
120
199
  ],
121
200
  "security": [
122
- "anti-bot", "antibot", "js challenge", "javascript challenge",
123
- "cdp detection", "runtime.enable", "puppeteer", "playwright",
124
- "selenium", "nodriver", "stealth", "undetected", "ja3", "ja4",
125
- "tls fingerprint", "fingerprinting", "curl_cffi", "got-scraping",
126
- "impersonate", "http/2 settings", "browser fingerprint",
127
- "canvas fingerprint", "webgl fingerprint", "navigator.webdriver",
128
- "audio context", "hardware concurrency", "waf bypass", "aws waf",
129
- "cloudflare bypass", "akamai bypass", "datadome", "perimeterx",
130
- "imperva bypass", "8kb bypass", "body size limit", "json sqli",
131
- "behavioral", "mouse movement", "ghost-cursor", "humanized",
132
- "flaresolverr", "turnstile", "rebrowser", "botbrowser",
201
+ "anti-bot",
202
+ "antibot",
203
+ "js challenge",
204
+ "javascript challenge",
205
+ "cdp detection",
206
+ "runtime.enable",
207
+ "puppeteer",
208
+ "playwright",
209
+ "selenium",
210
+ "nodriver",
211
+ "stealth",
212
+ "undetected",
213
+ "ja3",
214
+ "ja4",
215
+ "tls fingerprint",
216
+ "fingerprinting",
217
+ "curl_cffi",
218
+ "got-scraping",
219
+ "impersonate",
220
+ "http/2 settings",
221
+ "browser fingerprint",
222
+ "canvas fingerprint",
223
+ "webgl fingerprint",
224
+ "navigator.webdriver",
225
+ "audio context",
226
+ "hardware concurrency",
227
+ "waf bypass",
228
+ "aws waf",
229
+ "cloudflare bypass",
230
+ "akamai bypass",
231
+ "datadome",
232
+ "perimeterx",
233
+ "imperva bypass",
234
+ "8kb bypass",
235
+ "body size limit",
236
+ "json sqli",
237
+ "behavioral",
238
+ "mouse movement",
239
+ "ghost-cursor",
240
+ "humanized",
241
+ "flaresolverr",
242
+ "turnstile",
243
+ "rebrowser",
244
+ "botbrowser",
133
245
  ],
134
246
  }
135
247
 
@@ -210,6 +322,7 @@ _DEFAULT_QUERY_EXPANSIONS = {
210
322
  # CONFIG DATACLASS
211
323
  # ============================================================================
212
324
 
325
+
213
326
  def _resolve_path(raw, default: Path) -> Path:
214
327
  """Resolve a path from YAML (string) or use default (Path)."""
215
328
  if raw is None:
@@ -225,43 +338,93 @@ class Config:
225
338
  """Central configuration for the RAG system — loads from config.yaml when available."""
226
339
 
227
340
  # Paths
228
- data_dir: Path = field(default_factory=lambda: _resolve_path(
229
- _get("paths", "data_dir", None), BASE_DIR / "data"
230
- ))
231
- chroma_dir: Path = field(default_factory=lambda: _resolve_path(
232
- _get("paths", "data_dir", None), BASE_DIR / "data"
233
- ) / "chroma_db")
234
- documents_dir: Path = field(default_factory=lambda: _resolve_path(
235
- _get("paths", "documents_dir", None), BASE_DIR / "documents"
236
- ))
341
+ data_dir: Path = field(default_factory=lambda: _resolve_path(_get("paths", "data_dir", None), BASE_DIR / "data"))
342
+ chroma_dir: Path = field(
343
+ default_factory=lambda: _resolve_path(_get("paths", "data_dir", None), BASE_DIR / "data") / "chroma_db"
344
+ )
345
+ documents_dir: Path = field(
346
+ default_factory=lambda: _resolve_path(_get("paths", "documents_dir", None), BASE_DIR / "documents")
347
+ )
237
348
 
238
349
  # Chunking
239
- chunk_size: int = field(default_factory=lambda: _get("documents", "chunking", {}).get("chunk_size", 1000) if isinstance(_get("documents", "chunking", {}), dict) else 1000)
240
- chunk_overlap: int = field(default_factory=lambda: _get("documents", "chunking", {}).get("chunk_overlap", 200) if isinstance(_get("documents", "chunking", {}), dict) else 200)
350
+ chunk_size: int = field(
351
+ default_factory=lambda: (
352
+ _get("documents", "chunking", {}).get("chunk_size", 1000)
353
+ if isinstance(_get("documents", "chunking", {}), dict)
354
+ else 1000
355
+ )
356
+ )
357
+ chunk_overlap: int = field(
358
+ default_factory=lambda: (
359
+ _get("documents", "chunking", {}).get("chunk_overlap", 200)
360
+ if isinstance(_get("documents", "chunking", {}), dict)
361
+ else 200
362
+ )
363
+ )
241
364
 
242
365
  # Embeddings
243
- embedding_model: str = field(default_factory=lambda: _get("models", "embedding", {}).get("model", "BAAI/bge-small-en-v1.5") if isinstance(_get("models", "embedding", {}), dict) else "BAAI/bge-small-en-v1.5")
244
- embedding_dim: int = field(default_factory=lambda: _get("models", "embedding", {}).get("dimensions", 384) if isinstance(_get("models", "embedding", {}), dict) else 384)
366
+ embedding_model: str = field(
367
+ default_factory=lambda: (
368
+ _get("models", "embedding", {}).get("model", "BAAI/bge-small-en-v1.5")
369
+ if isinstance(_get("models", "embedding", {}), dict)
370
+ else "BAAI/bge-small-en-v1.5"
371
+ )
372
+ )
373
+ embedding_dim: int = field(
374
+ default_factory=lambda: (
375
+ _get("models", "embedding", {}).get("dimensions", 384)
376
+ if isinstance(_get("models", "embedding", {}), dict)
377
+ else 384
378
+ )
379
+ )
245
380
 
246
381
  # Reranker
247
- reranker_model: str = field(default_factory=lambda: _get("models", "reranker", {}).get("model", "Xenova/ms-marco-MiniLM-L-6-v2") if isinstance(_get("models", "reranker", {}), dict) else "Xenova/ms-marco-MiniLM-L-6-v2")
248
- reranker_enabled: bool = field(default_factory=lambda: _get("models", "reranker", {}).get("enabled", True) if isinstance(_get("models", "reranker", {}), dict) else True)
249
- reranker_top_k_multiplier: int = field(default_factory=lambda: _get("models", "reranker", {}).get("top_k_multiplier", 3) if isinstance(_get("models", "reranker", {}), dict) else 3)
382
+ reranker_model: str = field(
383
+ default_factory=lambda: (
384
+ _get("models", "reranker", {}).get("model", "Xenova/ms-marco-MiniLM-L-6-v2")
385
+ if isinstance(_get("models", "reranker", {}), dict)
386
+ else "Xenova/ms-marco-MiniLM-L-6-v2"
387
+ )
388
+ )
389
+ reranker_enabled: bool = field(
390
+ default_factory=lambda: (
391
+ _get("models", "reranker", {}).get("enabled", True)
392
+ if isinstance(_get("models", "reranker", {}), dict)
393
+ else True
394
+ )
395
+ )
396
+ reranker_top_k_multiplier: int = field(
397
+ default_factory=lambda: (
398
+ _get("models", "reranker", {}).get("top_k_multiplier", 3)
399
+ if isinstance(_get("models", "reranker", {}), dict)
400
+ else 3
401
+ )
402
+ )
250
403
 
251
404
  # ChromaDB
252
405
  collection_name: str = field(default_factory=lambda: _get("search", "collection_name", "knowledge_base"))
253
406
 
254
407
  # Supported formats
255
- supported_formats: List[str] = field(default_factory=lambda: _get("documents", "supported_formats", [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"]))
408
+ supported_formats: List[str] = field(
409
+ default_factory=lambda: _get(
410
+ "documents", "supported_formats", [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"]
411
+ )
412
+ )
256
413
 
257
414
  # Category mappings
258
- category_mappings: Dict[str, str] = field(default_factory=lambda: _yaml.get("category_mappings", _DEFAULT_CATEGORY_MAPPINGS))
415
+ category_mappings: Dict[str, str] = field(
416
+ default_factory=lambda: _get_top("category_mappings", _DEFAULT_CATEGORY_MAPPINGS)
417
+ )
259
418
 
260
419
  # Keyword routes
261
- keyword_routes: Dict[str, List[str]] = field(default_factory=lambda: _yaml.get("keyword_routes", _DEFAULT_KEYWORD_ROUTES))
420
+ keyword_routes: Dict[str, List[str]] = field(
421
+ default_factory=lambda: _get_top("keyword_routes", _DEFAULT_KEYWORD_ROUTES)
422
+ )
262
423
 
263
424
  # Query expansions
264
- query_expansions: Dict[str, List[str]] = field(default_factory=lambda: _yaml.get("query_expansions", _DEFAULT_QUERY_EXPANSIONS))
425
+ query_expansions: Dict[str, List[str]] = field(
426
+ default_factory=lambda: _get_top("query_expansions", _DEFAULT_QUERY_EXPANSIONS)
427
+ )
265
428
 
266
429
  # Search settings
267
430
  default_results: int = field(default_factory=lambda: _get("search", "default_results", 5))
@@ -21,8 +21,6 @@ Features:
21
21
  Autor: Lyon (Ailton Rocha)
22
22
  Versao: 3.0.0
23
23
  Data: 2026-03-19
24
-
25
- By Lyon :) Legal Ne?
26
24
  """
27
25
 
28
26
  import hashlib
@@ -1827,8 +1825,53 @@ def evaluate_retrieval(test_cases: str) -> str:
1827
1825
  # =============================================================================
1828
1826
 
1829
1827
 
1828
+ def _handle_init():
1829
+ """Export config template and presets to current directory."""
1830
+ import shutil
1831
+
1832
+ data_dir = Path(__file__).parent / "data"
1833
+ if not data_dir.exists():
1834
+ print("[ERROR] Bundled data not found. If installed from git, use presets/ directly.")
1835
+ return
1836
+
1837
+ cwd = Path.cwd()
1838
+
1839
+ # Copy config.example.yaml
1840
+ src = data_dir / "config.example.yaml"
1841
+ if src.exists():
1842
+ dst = cwd / "config.example.yaml"
1843
+ shutil.copy2(src, dst)
1844
+ print(f"[OK] {dst}")
1845
+
1846
+ # Copy presets
1847
+ presets_dir = cwd / "presets"
1848
+ presets_dir.mkdir(exist_ok=True)
1849
+ for f in data_dir.glob("*.yaml"):
1850
+ if f.name == "config.example.yaml":
1851
+ continue
1852
+ dst = presets_dir / f.name
1853
+ shutil.copy2(f, dst)
1854
+ print(f"[OK] {dst}")
1855
+
1856
+ # Create documents dir
1857
+ docs_dir = cwd / "documents"
1858
+ docs_dir.mkdir(exist_ok=True)
1859
+ print(f"[OK] {docs_dir}/")
1860
+
1861
+ print("\nDone. Quick start:")
1862
+ print(" cp presets/general.yaml config.yaml # or cybersecurity, developer, research")
1863
+ print(" # Add your documents to documents/")
1864
+ print(" # Restart Claude Code")
1865
+
1866
+
1830
1867
  def main():
1831
1868
  """Run the MCP server"""
1869
+ import sys
1870
+
1871
+ if len(sys.argv) > 1 and sys.argv[1] == "init":
1872
+ _handle_init()
1873
+ return
1874
+
1832
1875
  orchestrator = get_orchestrator()
1833
1876
 
1834
1877
  # Migration: check dimension mismatch AFTER full init (avoids segfault during __init__)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "knowledge-rag"
7
- version = "3.3.0"
7
+ version = "3.3.1"
8
8
  description = "Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 12 MCP Tools. Zero external servers."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -55,6 +55,13 @@ knowledge-rag = "mcp_server.server:main"
55
55
  [tool.hatch.build.targets.wheel]
56
56
  packages = ["mcp_server"]
57
57
 
58
+ [tool.hatch.build.targets.wheel.force-include]
59
+ "config.example.yaml" = "mcp_server/data/config.example.yaml"
60
+ "presets/cybersecurity.yaml" = "mcp_server/data/cybersecurity.yaml"
61
+ "presets/developer.yaml" = "mcp_server/data/developer.yaml"
62
+ "presets/research.yaml" = "mcp_server/data/research.yaml"
63
+ "presets/general.yaml" = "mcp_server/data/general.yaml"
64
+
58
65
  [tool.hatch.build.targets.sdist]
59
66
  include = [
60
67
  "mcp_server/",
File without changes
File without changes