ocerebro 0.3.2 → 0.4.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/cerebro/index/entities.db +0 -0
- package/package.json +1 -1
- package/pyproject.toml +3 -1
- package/src/dashboard/__init__.py +1 -0
- package/src/dashboard/api.py +404 -0
- package/src/dashboard/server.py +179 -0
- package/src/dashboard/static/index.html +519 -0
- package/src/dashboard/static/style.css +579 -0
- package/src/index/entities_db.py +5 -1
- package/src/mcp/server.py +95 -32
package/src/mcp/server.py
CHANGED
|
@@ -371,6 +371,20 @@ class CerebroMCP:
|
|
|
371
371
|
},
|
|
372
372
|
"required": ["entity"]
|
|
373
373
|
}
|
|
374
|
+
),
|
|
375
|
+
Tool(
|
|
376
|
+
name="cerebro_dashboard",
|
|
377
|
+
description="Abre o dashboard visual do OCerebro no browser (localhost:7999) - interface gráfica para explorar memórias, grafo de entidades e timeline",
|
|
378
|
+
inputSchema={
|
|
379
|
+
"type": "object",
|
|
380
|
+
"properties": {
|
|
381
|
+
"port": {
|
|
382
|
+
"type": "integer",
|
|
383
|
+
"description": "Porta do servidor (padrão: 7999)",
|
|
384
|
+
"default": 7999
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
374
388
|
)
|
|
375
389
|
]
|
|
376
390
|
|
|
@@ -410,6 +424,8 @@ class CerebroMCP:
|
|
|
410
424
|
result = self._capture_memory(arguments)
|
|
411
425
|
elif name == "cerebro_graph":
|
|
412
426
|
result = self._cerebro_graph(arguments)
|
|
427
|
+
elif name == "cerebro_dashboard":
|
|
428
|
+
result = self._cerebro_dashboard(arguments)
|
|
413
429
|
else:
|
|
414
430
|
return [TextContent(type="text", text=f"Ferramenta desconhecida: {name}")]
|
|
415
431
|
|
|
@@ -693,6 +709,7 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
693
709
|
def _capture_memory(self, args: Dict[str, Any]) -> str:
|
|
694
710
|
"""Salva uma memória no diretório nativo do Claude Code."""
|
|
695
711
|
import re
|
|
712
|
+
import yaml
|
|
696
713
|
from datetime import datetime
|
|
697
714
|
from src.core.paths import get_memory_index
|
|
698
715
|
|
|
@@ -711,15 +728,36 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
711
728
|
file_path = mem_dir / f"{mem_name}.md"
|
|
712
729
|
file_path.write_text(content, encoding="utf-8")
|
|
713
730
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
731
|
+
# Parse frontmatter uma única vez com yaml.safe_load
|
|
732
|
+
frontmatter_match = re.match(r'^---\n(.*?)\n---\n(.*)$', content, re.DOTALL)
|
|
733
|
+
frontmatter = {}
|
|
734
|
+
body_content = ""
|
|
735
|
+
if frontmatter_match:
|
|
736
|
+
try:
|
|
737
|
+
frontmatter = yaml.safe_load(frontmatter_match.group(1)) or {}
|
|
738
|
+
body_content = frontmatter_match.group(2)
|
|
739
|
+
except Exception:
|
|
740
|
+
pass # Fallback para regex se yaml falhar
|
|
741
|
+
|
|
742
|
+
# Extrai variáveis do frontmatter parseado (fallback para regex se necessário)
|
|
743
|
+
m_type = frontmatter.get('type', '')
|
|
744
|
+
project = frontmatter.get('project', '')
|
|
745
|
+
tags = frontmatter.get('tags', '')
|
|
746
|
+
desc = frontmatter.get('description', '')
|
|
747
|
+
|
|
748
|
+
# Fallback via regex se frontmatter parsing falhou
|
|
749
|
+
if not m_type:
|
|
750
|
+
type_match = re.search(r'type:\s*(.*)', content)
|
|
751
|
+
m_type = type_match.group(1).strip() if type_match else "project"
|
|
752
|
+
if not project:
|
|
753
|
+
project_match = re.search(r'project:\s*(.*)', content)
|
|
754
|
+
project = project_match.group(1).strip() if project_match else "unknown"
|
|
755
|
+
if not tags:
|
|
756
|
+
tags_match = re.search(r'tags:\s*(.*)', content)
|
|
757
|
+
tags = tags_match.group(1).strip() if tags_match else ""
|
|
758
|
+
if not desc:
|
|
759
|
+
desc_match = re.search(r'description:\s*(.*)', content)
|
|
760
|
+
desc = desc_match.group(1).strip() if desc_match else "sem descrição"
|
|
723
761
|
|
|
724
762
|
ts = datetime.now().strftime("%Y-%m-%d")
|
|
725
763
|
entry = f"- [{m_type}] {mem_name}.md ({ts}): {desc}\n"
|
|
@@ -733,30 +771,25 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
733
771
|
else:
|
|
734
772
|
index_path.write_text(f"# Memórias do Projeto\n\n{entry}", encoding="utf-8")
|
|
735
773
|
|
|
736
|
-
#
|
|
774
|
+
# Registrar entidades no grafo (frontmatter + conteúdo)
|
|
737
775
|
# ORDEM IMPORTANTE: content primeiro, frontmatter depois
|
|
738
|
-
# extract_from_content() deleta entidades
|
|
739
|
-
if self.entities_db:
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
frontmatter=frontmatter or {},
|
|
756
|
-
project=project
|
|
757
|
-
)
|
|
758
|
-
except Exception as e:
|
|
759
|
-
pass # Falha silenciosa se frontmatter inválido
|
|
776
|
+
# extract_from_content() deleta apenas entidades 'content', preservando 'frontmatter'
|
|
777
|
+
if self.entities_db and frontmatter_match:
|
|
778
|
+
try:
|
|
779
|
+
# 1. Extrai entidades do conteúdo (spaCy NER)
|
|
780
|
+
self.entities_db.extract_from_content(
|
|
781
|
+
memory_id=mem_name,
|
|
782
|
+
content=body_content,
|
|
783
|
+
use_spacy=True
|
|
784
|
+
)
|
|
785
|
+
# 2. Extrai entidades do frontmatter - preservadas
|
|
786
|
+
self.entities_db.extract_from_frontmatter(
|
|
787
|
+
memory_id=mem_name,
|
|
788
|
+
frontmatter=frontmatter,
|
|
789
|
+
project=project
|
|
790
|
+
)
|
|
791
|
+
except Exception:
|
|
792
|
+
pass # Falha silenciosa se frontmatter inválido
|
|
760
793
|
|
|
761
794
|
return f"✅ Memória '{mem_name}' salva em {file_path}"
|
|
762
795
|
|
|
@@ -862,6 +895,36 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
862
895
|
|
|
863
896
|
return "\n".join(lines)
|
|
864
897
|
|
|
898
|
+
def _cerebro_dashboard(self, args: Dict[str, Any]) -> str:
|
|
899
|
+
"""Abre o dashboard visual do OCerebro no browser"""
|
|
900
|
+
try:
|
|
901
|
+
from src.dashboard.server import DashboardServer
|
|
902
|
+
|
|
903
|
+
port = args.get("port", 7999)
|
|
904
|
+
|
|
905
|
+
# Instancia servidor
|
|
906
|
+
dashboard_server = DashboardServer(
|
|
907
|
+
cerebro_path=self.cerebro_path,
|
|
908
|
+
metadata_db=self.metadata_db,
|
|
909
|
+
embeddings_db=self.embeddings_db,
|
|
910
|
+
entities_db=self.entities_db
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
# Inicia se não estiver rodando
|
|
914
|
+
if not dashboard_server.is_running(port):
|
|
915
|
+
started = dashboard_server.start(port)
|
|
916
|
+
if not started:
|
|
917
|
+
return "⚠️ Não foi possível iniciar o servidor do dashboard. Verifique se a porta está disponível."
|
|
918
|
+
|
|
919
|
+
# Abre browser
|
|
920
|
+
dashboard_server.open_browser(port)
|
|
921
|
+
|
|
922
|
+
return f"✅ Dashboard aberto em http://localhost:{port}"
|
|
923
|
+
except ImportError as e:
|
|
924
|
+
return f"Erro: Não foi possível importar o dashboard. Verifique se fastapi e uvicorn estão instalados. ({e})"
|
|
925
|
+
except Exception as e:
|
|
926
|
+
return f"Erro ao abrir dashboard: {e}"
|
|
927
|
+
|
|
865
928
|
|
|
866
929
|
async def main():
|
|
867
930
|
"""Entry point do MCP Server"""
|