nexo-brain 7.25.3 → 7.25.4
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/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/cli.py +66 -0
- package/src/db/_schema.py +23 -0
- package/src/local_context/__init__.py +10 -0
- package/src/local_context/api.py +832 -47
- package/src/local_context/db.py +45 -1
- package/src/local_context/extractors.py +17 -1
- package/src/server.py +26 -0
- package/tool-enforcement-map.json +31 -0
package/src/local_context/db.py
CHANGED
|
@@ -18,6 +18,7 @@ MAIN_CLEANUP_STATE_KEY = "local_context_main_tables_drained"
|
|
|
18
18
|
LOCAL_CONTEXT_TABLES: tuple[str, ...] = (
|
|
19
19
|
"local_index_roots",
|
|
20
20
|
"local_index_exclusions",
|
|
21
|
+
"local_index_file_type_rules",
|
|
21
22
|
"local_index_jobs",
|
|
22
23
|
"local_index_checkpoints",
|
|
23
24
|
"local_index_state",
|
|
@@ -103,10 +104,53 @@ def _ensure_schema(conn: sqlite3.Connection) -> None:
|
|
|
103
104
|
_m63_local_context_layer(conn)
|
|
104
105
|
_m64_local_context_live_dirs(conn)
|
|
105
106
|
_ensure_entity_dossier_schema(conn)
|
|
106
|
-
conn
|
|
107
|
+
_ensure_local_context_v2_schema(conn)
|
|
108
|
+
conn.execute("PRAGMA user_version=65")
|
|
107
109
|
conn.commit()
|
|
108
110
|
|
|
109
111
|
|
|
112
|
+
def _table_columns(conn: sqlite3.Connection, table: str) -> set[str]:
|
|
113
|
+
rows = conn.execute(f"PRAGMA table_info({table})").fetchall()
|
|
114
|
+
return {str(row["name"] if isinstance(row, sqlite3.Row) else row[1]) for row in rows}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _add_column_if_missing(conn: sqlite3.Connection, table: str, column: str, definition: str) -> None:
|
|
118
|
+
if column in _table_columns(conn, table):
|
|
119
|
+
return
|
|
120
|
+
conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {definition}")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _ensure_local_context_v2_schema(conn: sqlite3.Connection) -> None:
|
|
124
|
+
"""Idempotent local-index v2 schema for managed roots and file type rules."""
|
|
125
|
+
_add_column_if_missing(conn, "local_index_roots", "source", "TEXT NOT NULL DEFAULT 'legacy'")
|
|
126
|
+
_add_column_if_missing(conn, "local_index_roots", "remote", "INTEGER NOT NULL DEFAULT 0")
|
|
127
|
+
_add_column_if_missing(conn, "local_index_roots", "seed_version", "INTEGER NOT NULL DEFAULT 1")
|
|
128
|
+
_add_column_if_missing(conn, "local_index_exclusions", "source", "TEXT NOT NULL DEFAULT 'legacy'")
|
|
129
|
+
_add_column_if_missing(conn, "local_index_exclusions", "kind", "TEXT NOT NULL DEFAULT 'folder'")
|
|
130
|
+
conn.executescript(
|
|
131
|
+
"""
|
|
132
|
+
CREATE TABLE IF NOT EXISTS local_index_file_type_rules (
|
|
133
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
134
|
+
extension TEXT NOT NULL,
|
|
135
|
+
action TEXT NOT NULL DEFAULT 'ignore',
|
|
136
|
+
source TEXT NOT NULL DEFAULT 'user',
|
|
137
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
138
|
+
reason TEXT NOT NULL DEFAULT '',
|
|
139
|
+
created_at REAL NOT NULL,
|
|
140
|
+
updated_at REAL NOT NULL,
|
|
141
|
+
UNIQUE(extension, source)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_local_index_roots_source
|
|
145
|
+
ON local_index_roots(source, status);
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_local_index_exclusions_source
|
|
147
|
+
ON local_index_exclusions(source);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS idx_local_index_file_type_rules_ext
|
|
149
|
+
ON local_index_file_type_rules(extension, source);
|
|
150
|
+
"""
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
110
154
|
def _ensure_entity_dossier_schema(conn: sqlite3.Connection) -> None:
|
|
111
155
|
conn.executescript(
|
|
112
156
|
"""
|
|
@@ -68,6 +68,20 @@ def _read_text(path: Path) -> str:
|
|
|
68
68
|
return data.decode("utf-8", errors="replace")[:MAX_CHARS]
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def _read_text_if_safe(path: Path) -> str:
|
|
72
|
+
data = path.read_bytes()[:MAX_TEXT_BYTES]
|
|
73
|
+
if not data or b"\x00" in data[:8192]:
|
|
74
|
+
return ""
|
|
75
|
+
text = _read_text(path)
|
|
76
|
+
if not text:
|
|
77
|
+
return ""
|
|
78
|
+
printable = sum(1 for char in text[:4096] if char.isprintable() or char.isspace())
|
|
79
|
+
sample_len = max(1, min(len(text), 4096))
|
|
80
|
+
if printable / sample_len < 0.85:
|
|
81
|
+
return ""
|
|
82
|
+
return text
|
|
83
|
+
|
|
84
|
+
|
|
71
85
|
def _extract_csv(path: Path) -> str:
|
|
72
86
|
text = _read_text(path)
|
|
73
87
|
rows = []
|
|
@@ -291,7 +305,9 @@ def extract_text(path: Path) -> tuple[str, dict]:
|
|
|
291
305
|
elif suffix == ".xlsx":
|
|
292
306
|
text = _extract_xlsx(path)
|
|
293
307
|
else:
|
|
294
|
-
text =
|
|
308
|
+
text = _read_text_if_safe(path)
|
|
309
|
+
if text:
|
|
310
|
+
metadata["extractor"] = "generic_text"
|
|
295
311
|
if contains_secret(text):
|
|
296
312
|
metadata["content_secret_detected"] = True
|
|
297
313
|
return clean_text(text), metadata
|
package/src/server.py
CHANGED
|
@@ -1026,6 +1026,32 @@ def nexo_local_index_exclusions(action: str = "list", path: str = "", reason: st
|
|
|
1026
1026
|
return json.dumps(result, ensure_ascii=False)
|
|
1027
1027
|
|
|
1028
1028
|
|
|
1029
|
+
@mcp.tool
|
|
1030
|
+
def nexo_local_index_filetypes(action: str = "list", extension: str = "", mode: str = "extract", reason: str = "user") -> str:
|
|
1031
|
+
"""List, include, exclude or reset local memory file extension rules."""
|
|
1032
|
+
normalized = str(action or "list").strip().lower()
|
|
1033
|
+
if normalized == "list":
|
|
1034
|
+
result = local_context_api.list_file_type_rules(readonly=True)
|
|
1035
|
+
elif normalized in {"include", "add"}:
|
|
1036
|
+
result = local_context_api.set_file_type_rule(extension, action=mode or "extract", reason=reason)
|
|
1037
|
+
elif normalized in {"exclude", "ignore"}:
|
|
1038
|
+
result = local_context_api.set_file_type_rule(extension, action="ignore", reason=reason)
|
|
1039
|
+
elif normalized in {"remove", "delete"}:
|
|
1040
|
+
result = local_context_api.remove_file_type_rule(extension)
|
|
1041
|
+
elif normalized == "reset":
|
|
1042
|
+
result = local_context_api.reset_file_type_rules()
|
|
1043
|
+
else:
|
|
1044
|
+
result = {"ok": False, "error": "unknown_action", "allowed": ["list", "include", "exclude", "remove", "reset"]}
|
|
1045
|
+
return json.dumps(result, ensure_ascii=False)
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
@mcp.tool
|
|
1049
|
+
def nexo_local_index_migrate_roots_v2(apply: bool = False) -> str:
|
|
1050
|
+
"""Plan or apply Local Memory roots v2 cleanup."""
|
|
1051
|
+
result = local_context_api.migrate_roots_seed_v2(dry_run=not bool(apply))
|
|
1052
|
+
return json.dumps(result, ensure_ascii=False)
|
|
1053
|
+
|
|
1054
|
+
|
|
1029
1055
|
@mcp.tool
|
|
1030
1056
|
def nexo_local_context(
|
|
1031
1057
|
query: str,
|
|
@@ -2502,6 +2502,37 @@
|
|
|
2502
2502
|
},
|
|
2503
2503
|
"triggers_after": []
|
|
2504
2504
|
},
|
|
2505
|
+
"nexo_local_index_filetypes": {
|
|
2506
|
+
"description": "List, include, exclude, remove, or reset Local Context file extension rules",
|
|
2507
|
+
"category": "local_context",
|
|
2508
|
+
"source": "server",
|
|
2509
|
+
"requires": [],
|
|
2510
|
+
"provides": [
|
|
2511
|
+
"local_index_file_type_rules"
|
|
2512
|
+
],
|
|
2513
|
+
"internal_calls": [],
|
|
2514
|
+
"enforcement": {
|
|
2515
|
+
"level": "none",
|
|
2516
|
+
"rules": []
|
|
2517
|
+
},
|
|
2518
|
+
"triggers_after": []
|
|
2519
|
+
},
|
|
2520
|
+
"nexo_local_index_migrate_roots_v2": {
|
|
2521
|
+
"description": "Plan or apply Local Context roots v2 cleanup",
|
|
2522
|
+
"category": "local_context",
|
|
2523
|
+
"source": "server",
|
|
2524
|
+
"requires": [],
|
|
2525
|
+
"provides": [
|
|
2526
|
+
"local_index_roots",
|
|
2527
|
+
"local_index_cleanup"
|
|
2528
|
+
],
|
|
2529
|
+
"internal_calls": [],
|
|
2530
|
+
"enforcement": {
|
|
2531
|
+
"level": "none",
|
|
2532
|
+
"rules": []
|
|
2533
|
+
},
|
|
2534
|
+
"triggers_after": []
|
|
2535
|
+
},
|
|
2505
2536
|
"nexo_local_index_models": {
|
|
2506
2537
|
"description": "Inspect or warm Local Context local model availability",
|
|
2507
2538
|
"category": "local_context",
|