own-rag-cli 0.0.1-snapshot

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,857 @@
1
+ #!/usr/bin/env bash
2
+ # =============================================================================
3
+ # chroma_monitor.sh — Monitor do banco de dados ChromaDB
4
+ # =============================================================================
5
+ # Uso:
6
+ # ./chroma_monitor.sh # menu interativo
7
+ # ./chroma_monitor.sh chunks # contagem de chunks (roda uma vez)
8
+ # ./chroma_monitor.sh watch # monitora chunks em tempo real (loop)
9
+ # ./chroma_monitor.sh disk # tamanho do banco no disco (watch)
10
+ # ./chroma_monitor.sh logs # logs em tempo real do container Docker
11
+ # ./chroma_monitor.sh mcp-logs # logs de uso das ferramentas MCP
12
+ # ./chroma_monitor.sh mcp-summary # resumo agregado de uso MCP (24h + geral)
13
+ # ./chroma_monitor.sh full # painel completo: chunks + disco + detalhes
14
+ # ./chroma_monitor.sh reset # zera o ChromaDB (remove todas as coleções)
15
+ # =============================================================================
16
+ set -euo pipefail
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Cores e formatação
20
+ # ---------------------------------------------------------------------------
21
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
22
+ BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'
23
+ BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Configuração
27
+ # ---------------------------------------------------------------------------
28
+ CHROMA_HOST="localhost"
29
+ CHROMA_PORT="8000"
30
+ RAG_DB_DIR="${HOME}/.rag_db"
31
+ VENV_DIR="${HOME}/.rag_venv"
32
+ VENV_PYTHON="${VENV_DIR}/bin/python3"
33
+ VENV_PIP="${VENV_DIR}/bin/pip"
34
+ CONTAINER_NAME="chromadb-rag"
35
+ MCP_USAGE_LOG="${MCP_USAGE_LOG:-${HOME}/.rag_db/mcp_usage.log}"
36
+ REFRESH_INTERVAL=5
37
+ UI_LANG="${CHROMA_MONITOR_LANG:-${RAG_SETUP_LANG:-}}"
38
+ YES_NO_HINT="[s/N]"
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Idioma e i18n
42
+ # ---------------------------------------------------------------------------
43
+
44
+ set_lang_defaults() {
45
+ if [[ "$UI_LANG" == "en-us" ]]; then
46
+ YES_NO_HINT="[y/N]"
47
+ else
48
+ YES_NO_HINT="[s/N]"
49
+ fi
50
+ }
51
+
52
+ select_ui_language() {
53
+ if [[ -n "$UI_LANG" ]]; then
54
+ UI_LANG="$(echo "$UI_LANG" | tr '[:upper:]' '[:lower:]')"
55
+ case "$UI_LANG" in
56
+ pt-br|pt|en-us|en) ;;
57
+ *) UI_LANG="pt-br" ;;
58
+ esac
59
+ [[ "$UI_LANG" == "pt" ]] && UI_LANG="pt-br"
60
+ [[ "$UI_LANG" == "en" ]] && UI_LANG="en-us"
61
+ set_lang_defaults
62
+ return
63
+ fi
64
+
65
+ if [[ ! -t 0 ]]; then
66
+ UI_LANG="pt-br"
67
+ set_lang_defaults
68
+ return
69
+ fi
70
+
71
+ echo ""
72
+ echo -e "${GREEN}Idioma / Language: [1] PT-BR [2] EN-US (padrão/default: 1)${NC}"
73
+ read -r -p "> " LANG_CHOICE
74
+ case "$LANG_CHOICE" in
75
+ 2|en|EN|en-us|EN-US|english|English) UI_LANG="en-us" ;;
76
+ *) UI_LANG="pt-br" ;;
77
+ esac
78
+ set_lang_defaults
79
+ }
80
+
81
+ t() {
82
+ local key="$1"
83
+ if [[ "$UI_LANG" == "en-us" ]]; then
84
+ case "$key" in
85
+ err_prefix) echo "ERROR" ;;
86
+ py_missing) echo "python3 not found. Install: sudo apt install python3 python3-venv" ;;
87
+ py_venv_missing) echo "python3-venv not found. Install: sudo apt install python3-venv" ;;
88
+ creating_venv) echo "Creating virtual environment at" ;;
89
+ venv_created) echo "Venv created." ;;
90
+ installing_chromadb) echo "Installing chromadb in venv (first time only)..." ;;
91
+ chromadb_installed) echo "chromadb installed." ;;
92
+ header_title) echo "ChromaDB Monitor — Local RAG" ;;
93
+ db_label) echo "Database" ;;
94
+ data_label) echo "Data" ;;
95
+ querying_chroma) echo "Querying ChromaDB at" ;;
96
+ press_ctrl_c) echo "Press Ctrl+C to exit." ;;
97
+ dir_not_found) echo "Directory not found:" ;;
98
+ db_not_init) echo "Database not initialized yet." ;;
99
+ monitoring_size) echo "Monitoring size of" ;;
100
+ files_in_db) echo "Files in database" ;;
101
+ size_label) echo "Size" ;;
102
+ internal_files_short) echo "Internal files" ;;
103
+ docker_not_found) echo "Docker not found." ;;
104
+ container_not_running) echo "Container is not running:" ;;
105
+ active_containers) echo "Active containers:" ;;
106
+ none) echo "None." ;;
107
+ showing_logs) echo "Showing logs from" ;;
108
+ logs_tip) echo "Tip: run indexer in another terminal to see incoming requests." ;;
109
+ mcp_log_missing) echo "MCP log file not found:" ;;
110
+ mcp_log_hint1) echo "File is created automatically when MCP server receives calls." ;;
111
+ mcp_log_hint2) echo "Restart/update mcp-rag-server and use an MCP tool." ;;
112
+ mcp_log_hint_short) echo "File is created automatically when MCP server receives calls." ;;
113
+ showing_mcp_usage) echo "Showing MCP usage in real time (Ctrl+C to exit)" ;;
114
+ source) echo "Source" ;;
115
+ panel_updates) echo "Panel updates every" ;;
116
+ container_docker) echo "Docker container" ;;
117
+ running) echo "running" ;;
118
+ started) echo "started" ;;
119
+ offline) echo "offline" ;;
120
+ start_with) echo "Start with" ;;
121
+ disk_db) echo "Database on disk" ;;
122
+ internal_files) echo "internal files in" ;;
123
+ collections_chunks) echo "Collections and chunks" ;;
124
+ last_update) echo "Last update" ;;
125
+ next_in) echo "next in" ;;
126
+ reset_attention) echo "[WARNING] This action will delete all ChromaDB collections and chunks." ;;
127
+ reset_irreversible) echo "This operation is irreversible." ;;
128
+ confirm_continue) echo "Continue?" ;;
129
+ confirm_type_reset) echo "Type RESET to confirm:" ;;
130
+ reset_cancelled) echo "Reset canceled." ;;
131
+ status_online) echo "Status: ● ChromaDB online" ;;
132
+ status_offline) echo "Status: ● ChromaDB offline" ;;
133
+ choose_option) echo "Choose an option:" ;;
134
+ menu_1) echo "[1] Count chunks now (single snapshot)" ;;
135
+ menu_2) echo "[2] Watch chunks in real time (updates every ${REFRESH_INTERVAL}s)" ;;
136
+ menu_3) echo "[3] Watch disk size (watch ~/.rag_db)" ;;
137
+ menu_4) echo "[4] Show Docker logs (HTTP requests to ChromaDB)" ;;
138
+ menu_5) echo "[5] Show MCP usage logs (actor + tool)" ;;
139
+ menu_6) echo "[6] MCP usage summary (top tools/actors + 24h)" ;;
140
+ menu_7) echo "[7] Full dashboard (all in one, auto refresh)" ;;
141
+ menu_8) echo "[8] Reset ChromaDB (deletes all collections)" ;;
142
+ menu_0) echo "[0] Exit" ;;
143
+ option_prompt) echo "Option:" ;;
144
+ invalid_option) echo "Invalid option." ;;
145
+ unknown_mode) echo "Unknown mode" ;;
146
+ usage) echo "Usage: $0 [chunks|watch|disk|logs|mcp-logs|mcp-summary|full|reset|menu]" ;;
147
+ *) echo "$key" ;;
148
+ esac
149
+ else
150
+ case "$key" in
151
+ err_prefix) echo "ERRO" ;;
152
+ py_missing) echo "python3 não encontrado. Instale: sudo apt install python3 python3-venv" ;;
153
+ py_venv_missing) echo "python3-venv não encontrado. Instale: sudo apt install python3-venv" ;;
154
+ creating_venv) echo "Criando ambiente virtual em" ;;
155
+ venv_created) echo "Venv criado." ;;
156
+ installing_chromadb) echo "Instalando chromadb no venv (necessário apenas na primeira vez)..." ;;
157
+ chromadb_installed) echo "chromadb instalado." ;;
158
+ header_title) echo "Monitor ChromaDB — RAG Local" ;;
159
+ db_label) echo "Banco" ;;
160
+ data_label) echo "Dados" ;;
161
+ querying_chroma) echo "Consultando ChromaDB em" ;;
162
+ press_ctrl_c) echo "Pressione Ctrl+C para sair." ;;
163
+ dir_not_found) echo "Diretório não encontrado:" ;;
164
+ db_not_init) echo "O banco ainda não foi inicializado." ;;
165
+ monitoring_size) echo "Monitorando tamanho de" ;;
166
+ files_in_db) echo "Arquivos no banco" ;;
167
+ size_label) echo "Tamanho" ;;
168
+ internal_files_short) echo "Arquivos internos" ;;
169
+ docker_not_found) echo "Docker não encontrado." ;;
170
+ container_not_running) echo "Container não está rodando:" ;;
171
+ active_containers) echo "Containers ativos:" ;;
172
+ none) echo "Nenhum." ;;
173
+ showing_logs) echo "Exibindo logs de" ;;
174
+ logs_tip) echo "Dica: rode o indexer em outro terminal para ver as requisições entrando." ;;
175
+ mcp_log_missing) echo "Arquivo de log MCP não encontrado:" ;;
176
+ mcp_log_hint1) echo "O arquivo será criado automaticamente quando o MCP Server receber chamadas." ;;
177
+ mcp_log_hint2) echo "Reinicie/atualize o mcp-rag-server e use alguma ferramenta MCP." ;;
178
+ mcp_log_hint_short) echo "O arquivo será criado automaticamente quando o MCP Server receber chamadas." ;;
179
+ showing_mcp_usage) echo "Exibindo uso MCP em tempo real (Ctrl+C para sair)" ;;
180
+ source) echo "Fonte" ;;
181
+ panel_updates) echo "Painel atualiza a cada" ;;
182
+ container_docker) echo "Container Docker" ;;
183
+ running) echo "rodando" ;;
184
+ started) echo "iniciado" ;;
185
+ offline) echo "offline" ;;
186
+ start_with) echo "Inicie com" ;;
187
+ disk_db) echo "Banco em disco" ;;
188
+ internal_files) echo "arquivos internos em" ;;
189
+ collections_chunks) echo "Coleções e Chunks" ;;
190
+ last_update) echo "Última atualização" ;;
191
+ next_in) echo "próxima em" ;;
192
+ reset_attention) echo "[ATENÇÃO] Esta ação vai apagar todas as coleções e chunks do ChromaDB." ;;
193
+ reset_irreversible) echo "Essa operação é irreversível." ;;
194
+ confirm_continue) echo "Deseja continuar?" ;;
195
+ confirm_type_reset) echo "Digite ZERAR para confirmar:" ;;
196
+ reset_cancelled) echo "Reset cancelado." ;;
197
+ status_online) echo "Status: ● ChromaDB online" ;;
198
+ status_offline) echo "Status: ● ChromaDB offline" ;;
199
+ choose_option) echo "Escolha uma opção:" ;;
200
+ menu_1) echo "[1] Contar chunks agora (snapshot único)" ;;
201
+ menu_2) echo "[2] Monitorar chunks em tempo real (atualiza a cada ${REFRESH_INTERVAL}s)" ;;
202
+ menu_3) echo "[3] Monitorar tamanho no disco (watch no ~/.rag_db)" ;;
203
+ menu_4) echo "[4] Ver logs do Docker (requisições HTTP ao ChromaDB)" ;;
204
+ menu_5) echo "[5] Ver logs de uso MCP (quem acessa + ferramenta usada)" ;;
205
+ menu_6) echo "[6] Resumo de uso MCP (top ferramentas/atores e 24h)" ;;
206
+ menu_7) echo "[7] Painel completo (tudo junto, atualiza automático)" ;;
207
+ menu_8) echo "[8] Zerar ChromaDB (apaga todas as coleções)" ;;
208
+ menu_0) echo "[0] Sair" ;;
209
+ option_prompt) echo "Opção:" ;;
210
+ invalid_option) echo "Opção inválida." ;;
211
+ unknown_mode) echo "Modo desconhecido" ;;
212
+ usage) echo "Uso: $0 [chunks|watch|disk|logs|mcp-logs|mcp-summary|full|reset|menu]" ;;
213
+ *) echo "$key" ;;
214
+ esac
215
+ fi
216
+ }
217
+
218
+ log_info() { echo -e "${GREEN}[+]${NC} $*"; }
219
+ log_warn() { echo -e "${YELLOW}[!]${NC} $*"; }
220
+ log_error() { echo -e "${RED}[$(t err_prefix)]${NC} $*" >&2; }
221
+
222
+ # ---------------------------------------------------------------------------
223
+ # Funções: venv e dependências
224
+ # ---------------------------------------------------------------------------
225
+
226
+ ensure_venv() {
227
+ # Garante que o venv existe e tem o chromadb instalado
228
+ if [[ ! -f "${VENV_PYTHON}" ]]; then
229
+ if ! command -v python3 &>/dev/null; then
230
+ log_error "$(t py_missing)"
231
+ exit 1
232
+ fi
233
+ if ! python3 -m venv --help &>/dev/null; then
234
+ log_error "$(t py_venv_missing)"
235
+ exit 1
236
+ fi
237
+ log_info "$(t creating_venv) ${VENV_DIR}..."
238
+ python3 -m venv "${VENV_DIR}"
239
+ log_info "$(t venv_created)"
240
+ fi
241
+
242
+ # Instala chromadb no venv se ainda não estiver lá
243
+ if ! "${VENV_PYTHON}" -c "import chromadb" 2>/dev/null; then
244
+ log_info "$(t installing_chromadb)"
245
+ "${VENV_PIP}" install --quiet --upgrade pip
246
+ "${VENV_PIP}" install --quiet chromadb
247
+ log_info "$(t chromadb_installed)"
248
+ fi
249
+ }
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # Funções auxiliares
253
+ # ---------------------------------------------------------------------------
254
+
255
+ is_chroma_running() {
256
+ "${VENV_PYTHON}" -c "
257
+ import chromadb, sys
258
+ try:
259
+ chromadb.HttpClient(host='${CHROMA_HOST}', port=${CHROMA_PORT}).heartbeat()
260
+ sys.exit(0)
261
+ except:
262
+ sys.exit(1)
263
+ " 2>/dev/null
264
+ }
265
+
266
+ print_header() {
267
+ clear
268
+ echo -e "${BOLD}${BLUE}"
269
+ echo " ╔══════════════════════════════════════════════════════╗"
270
+ printf " ║ %-44s║\n" "$(t header_title)"
271
+ echo " ╚══════════════════════════════════════════════════════╝"
272
+ echo -e "${NC}"
273
+ echo -e " ${DIM}$(t db_label): ${CHROMA_HOST}:${CHROMA_PORT} | $(t data_label): ${RAG_DB_DIR}${NC}"
274
+ echo ""
275
+ }
276
+
277
+ # ---------------------------------------------------------------------------
278
+ # Modo 1: chunks — contagem detalhada (roda uma vez)
279
+ # ---------------------------------------------------------------------------
280
+ cmd_chunks() {
281
+ ensure_venv
282
+
283
+ echo ""
284
+ echo -e "${BOLD}${CYAN} $(t querying_chroma) ${CHROMA_HOST}:${CHROMA_PORT}...${NC}"
285
+ echo ""
286
+
287
+ MONITOR_LANG="${UI_LANG}" "${VENV_PYTHON}" - << 'PYEOF'
288
+ import os
289
+ import sys
290
+ import chromadb
291
+
292
+ CHROMA_HOST = "localhost"
293
+ CHROMA_PORT = 8000
294
+ LANG = os.getenv("MONITOR_LANG", "pt-br").lower()
295
+ IS_EN = LANG == "en-us"
296
+
297
+ BOLD = "\033[1m"
298
+ GREEN = "\033[0;32m"
299
+ CYAN = "\033[0;36m"
300
+ YELLOW = "\033[1;33m"
301
+ RED = "\033[0;31m"
302
+ DIM = "\033[2m"
303
+ NC = "\033[0m"
304
+
305
+ try:
306
+ client = chromadb.HttpClient(host=CHROMA_HOST, port=CHROMA_PORT)
307
+ client.heartbeat()
308
+ except Exception as e:
309
+ if IS_EN:
310
+ print(f"{RED}[ERROR] ChromaDB offline or unreachable: {e}{NC}")
311
+ print(" Check: docker ps | grep chromadb")
312
+ else:
313
+ print(f"{RED}[ERRO] ChromaDB offline ou inacessível: {e}{NC}")
314
+ print(" Verifique: docker ps | grep chromadb")
315
+ sys.exit(1)
316
+
317
+ collections = client.list_collections()
318
+
319
+ if not collections:
320
+ if IS_EN:
321
+ print(f"{YELLOW} No collections found.{NC}")
322
+ print(" Run indexer to populate database.")
323
+ else:
324
+ print(f"{YELLOW} Nenhuma coleção encontrada.{NC}")
325
+ print(" Execute o indexer para popular o banco.")
326
+ sys.exit(0)
327
+
328
+ total_global = 0
329
+
330
+ print(f" {('Collection' if IS_EN else 'Coleção'):<25} {'Chunks':>10} {('Details' if IS_EN else 'Detalhes')}")
331
+ print(f" {'-'*60}")
332
+
333
+ for col in collections:
334
+ count = col.count()
335
+ total_global += count
336
+
337
+ sample = col.get(limit=1, include=["metadatas"])
338
+ meta_keys = []
339
+ if sample["metadatas"]:
340
+ meta_keys = list(sample["metadatas"][0].keys())
341
+
342
+ all_meta = col.get(include=["metadatas"])
343
+ unique_files = set()
344
+ for m in all_meta["metadatas"]:
345
+ if m and "file_path" in m:
346
+ unique_files.add(m["file_path"])
347
+
348
+ file_label = "unique files" if IS_EN else "arquivos únicos"
349
+ print(f" {GREEN}{BOLD}{col.name:<25}{NC} {CYAN}{count:>7} chunks{NC} "
350
+ f"{DIM}({len(unique_files)} {file_label}){NC}")
351
+ if meta_keys:
352
+ fields_label = "fields" if IS_EN else "campos"
353
+ print(f" {'':25} {'':10} {DIM}{fields_label}: {', '.join(meta_keys)}{NC}")
354
+ print()
355
+
356
+ print(f" {'-'*60}")
357
+ if IS_EN:
358
+ print(f" {BOLD}Global total: {CYAN}{total_global} chunks{NC}{BOLD} in {len(collections)} collection(s){NC}")
359
+ else:
360
+ print(f" {BOLD}Total global: {CYAN}{total_global} chunks{NC}{BOLD} em {len(collections)} coleção(ões){NC}")
361
+ print()
362
+ PYEOF
363
+ }
364
+
365
+ # ---------------------------------------------------------------------------
366
+ # Modo 2: watch — atualiza contagem a cada N segundos
367
+ # ---------------------------------------------------------------------------
368
+ cmd_watch() {
369
+ ensure_venv
370
+
371
+ echo -e "${DIM} $(t press_ctrl_c)${NC}"
372
+ echo ""
373
+
374
+ LAST_COUNT=-1
375
+
376
+ while true; do
377
+ RESULT=$("${VENV_PYTHON}" - 2>/dev/null << 'PYEOF'
378
+ import chromadb, sys
379
+
380
+ try:
381
+ client = chromadb.HttpClient(host="localhost", port=8000)
382
+ client.heartbeat()
383
+ cols = client.list_collections()
384
+ total = sum(c.count() for c in cols)
385
+ details = " | ".join(f"{c.name}: {c.count()}" for c in cols)
386
+ print(f"{total}||{details}||online")
387
+ except:
388
+ print("0||||offline")
389
+ PYEOF
390
+ )
391
+
392
+ TOTAL=$(echo "$RESULT" | cut -d'|' -f1)
393
+ DETAILS=$(echo "$RESULT" | cut -d'|' -f3)
394
+ STATUS=$(echo "$RESULT" | cut -d'|' -f5)
395
+ TIMESTAMP=$(date '+%H:%M:%S')
396
+
397
+ if [[ "$STATUS" == "offline" ]]; then
398
+ printf "\r ${RED}[%s]${NC} ChromaDB offline...%20s" "$TIMESTAMP" ""
399
+ else
400
+ DIFF=""
401
+ if [[ "$LAST_COUNT" -ge 0 ]] && [[ "$TOTAL" -ne "$LAST_COUNT" ]]; then
402
+ DELTA=$((TOTAL - LAST_COUNT))
403
+ if [[ "$DELTA" -gt 0 ]]; then
404
+ DIFF=" ${GREEN}(+${DELTA})${NC}"
405
+ else
406
+ DIFF=" ${RED}(${DELTA})${NC}"
407
+ fi
408
+ fi
409
+
410
+ printf "\r ${BOLD}[%s]${NC} Chunks: ${CYAN}${BOLD}%s${NC}%b ${DIM}%s${NC}%30s" \
411
+ "$TIMESTAMP" "$TOTAL" "$DIFF" "$DETAILS" ""
412
+
413
+ LAST_COUNT="$TOTAL"
414
+ fi
415
+
416
+ sleep "$REFRESH_INTERVAL"
417
+ done
418
+ }
419
+
420
+ # ---------------------------------------------------------------------------
421
+ # Modo 3: disk — monitora tamanho do banco no disco
422
+ # ---------------------------------------------------------------------------
423
+ cmd_disk() {
424
+ if [[ ! -d "$RAG_DB_DIR" ]]; then
425
+ echo -e "${YELLOW}[!]${NC} $(t dir_not_found) ${RAG_DB_DIR}"
426
+ echo -e " $(t db_not_init)"
427
+ exit 1
428
+ fi
429
+
430
+ echo -e "${DIM} $(t monitoring_size) ${RAG_DB_DIR} (Ctrl+C)${NC}"
431
+ echo ""
432
+
433
+ if command -v watch &>/dev/null; then
434
+ watch -n "$REFRESH_INTERVAL" -d \
435
+ "du -sh ${RAG_DB_DIR} && echo '' && find ${RAG_DB_DIR} -type f | wc -l | xargs -I{} echo '$(t files_in_db): {}'"
436
+ else
437
+ while true; do
438
+ SIZE=$(du -sh "$RAG_DB_DIR" 2>/dev/null | cut -f1)
439
+ FILES=$(find "$RAG_DB_DIR" -type f 2>/dev/null | wc -l)
440
+ printf "\r ${BOLD}[%s]${NC} %s: ${CYAN}${BOLD}%s${NC} | %s: ${DIM}%s${NC}%20s" \
441
+ "$(date '+%H:%M:%S')" "$(t size_label)" "$SIZE" "$(t internal_files_short)" "$FILES" ""
442
+ sleep "$REFRESH_INTERVAL"
443
+ done
444
+ fi
445
+ }
446
+
447
+ # ---------------------------------------------------------------------------
448
+ # Modo 4: logs — logs do container Docker em tempo real
449
+ # ---------------------------------------------------------------------------
450
+ cmd_logs() {
451
+ if ! command -v docker &>/dev/null; then
452
+ log_error "$(t docker_not_found)"
453
+ exit 1
454
+ fi
455
+
456
+ if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
457
+ echo -e "${YELLOW}[!]${NC} $(t container_not_running) '${CONTAINER_NAME}'."
458
+ echo ""
459
+ echo -e " $(t active_containers)"
460
+ docker ps --format " - {{.Names}} ({{.Status}})" 2>/dev/null || echo " $(t none)"
461
+ exit 1
462
+ fi
463
+
464
+ echo -e "${DIM} $(t showing_logs) '${CONTAINER_NAME}' (Ctrl+C)${NC}"
465
+ echo -e "${DIM} $(t logs_tip)${NC}"
466
+ echo ""
467
+ docker logs -f --tail=30 "$CONTAINER_NAME"
468
+ }
469
+
470
+ # ---------------------------------------------------------------------------
471
+ # Modo 5: mcp-logs — logs de uso do MCP (quem acessa + ferramenta)
472
+ # ---------------------------------------------------------------------------
473
+ cmd_mcp_logs() {
474
+ ensure_venv
475
+
476
+ if [[ ! -f "$MCP_USAGE_LOG" ]]; then
477
+ echo -e "${YELLOW}[!]${NC} $(t mcp_log_missing) ${MCP_USAGE_LOG}"
478
+ echo -e " $(t mcp_log_hint1)"
479
+ echo -e " $(t mcp_log_hint2)"
480
+ exit 1
481
+ fi
482
+
483
+ echo -e "${DIM} $(t showing_mcp_usage)${NC}"
484
+ echo -e "${DIM} $(t source): ${MCP_USAGE_LOG}${NC}"
485
+ echo ""
486
+
487
+ # Formata JSONL em linhas legíveis:
488
+ # [timestamp] actor=<ator> tool=<ferramenta> event=<start/end> status=<status> client=<processo>
489
+ tail -n 50 -f "$MCP_USAGE_LOG" | while IFS= read -r line; do
490
+ [[ -z "$line" ]] && continue
491
+ "${VENV_PYTHON}" - "$line" <<'PYEOF'
492
+ import json
493
+ import sys
494
+
495
+ raw = sys.argv[1]
496
+ try:
497
+ data = json.loads(raw)
498
+ except Exception:
499
+ print(raw)
500
+ sys.exit(0)
501
+
502
+ ts = data.get("timestamp", "-")
503
+ actor = data.get("actor", "-")
504
+ tool = data.get("tool", "-")
505
+ event = data.get("event", "-")
506
+ client = data.get("client_process", "-")
507
+ details = data.get("details", {}) or {}
508
+ status = details.get("status", "-")
509
+
510
+ print(f"[{ts}] actor={actor} tool={tool} event={event} status={status} client={client}")
511
+ PYEOF
512
+ done
513
+ }
514
+
515
+ # ---------------------------------------------------------------------------
516
+ # Modo 6: mcp-summary — visão agregada de uso do MCP
517
+ # ---------------------------------------------------------------------------
518
+ cmd_mcp_summary() {
519
+ ensure_venv
520
+
521
+ if [[ ! -f "$MCP_USAGE_LOG" ]]; then
522
+ echo -e "${YELLOW}[!]${NC} $(t mcp_log_missing) ${MCP_USAGE_LOG}"
523
+ echo -e " $(t mcp_log_hint_short)"
524
+ exit 1
525
+ fi
526
+
527
+ MONITOR_LANG="${UI_LANG}" "${VENV_PYTHON}" - "$MCP_USAGE_LOG" <<'PYEOF'
528
+ import json
529
+ import os
530
+ import sys
531
+ from collections import Counter
532
+ from datetime import datetime, timedelta, timezone
533
+
534
+ LANG = os.getenv("MONITOR_LANG", "pt-br").lower()
535
+ IS_EN = LANG == "en-us"
536
+
537
+ path = sys.argv[1]
538
+ now = datetime.now(timezone.utc)
539
+ window_24h = now - timedelta(hours=24)
540
+
541
+ total_events = 0
542
+ total_calls_end = 0
543
+ ok_calls = 0
544
+ error_calls = 0
545
+
546
+ tools_all = Counter()
547
+ actors_all = Counter()
548
+ tools_24h = Counter()
549
+ actors_24h = Counter()
550
+ status_24h = Counter()
551
+
552
+ def parse_ts(raw: str):
553
+ if not raw:
554
+ return None
555
+ try:
556
+ return datetime.fromisoformat(raw.replace("Z", "+00:00"))
557
+ except Exception:
558
+ return None
559
+
560
+ with open(path, "r", encoding="utf-8") as f:
561
+ for line in f:
562
+ line = line.strip()
563
+ if not line:
564
+ continue
565
+ try:
566
+ item = json.loads(line)
567
+ except Exception:
568
+ continue
569
+
570
+ total_events += 1
571
+ tool = item.get("tool", "-")
572
+ actor = item.get("actor", "-")
573
+ event = item.get("event", "-")
574
+ details = item.get("details", {}) or {}
575
+ status = details.get("status", "-")
576
+ ts = parse_ts(item.get("timestamp", ""))
577
+
578
+ if event == "tool_call_end":
579
+ total_calls_end += 1
580
+ if status == "ok":
581
+ ok_calls += 1
582
+ elif status == "error":
583
+ error_calls += 1
584
+ tools_all[tool] += 1
585
+ actors_all[actor] += 1
586
+
587
+ if ts and ts >= window_24h:
588
+ tools_24h[tool] += 1
589
+ actors_24h[actor] += 1
590
+ status_24h[status] += 1
591
+
592
+ print("MCP usage summary" if IS_EN else "Resumo de uso MCP")
593
+ print("-" * 72)
594
+ print(f"{'Log file' if IS_EN else 'Arquivo de log'}: {path}")
595
+ print(f"{'Total events (start/end)' if IS_EN else 'Eventos totais (start/end)'}: {total_events}")
596
+ print(f"{'Completed calls (tool_call_end)' if IS_EN else 'Chamadas finalizadas (tool_call_end)'}: {total_calls_end}")
597
+ print(f"{'Completed status' if IS_EN else 'Status finalizadas'}: ok={ok_calls} | error={error_calls}")
598
+ print()
599
+
600
+ print("Top tools (overall)" if IS_EN else "Top ferramentas (geral)")
601
+ if tools_all:
602
+ for name, count in tools_all.most_common(10):
603
+ print(f" - {name}: {count}")
604
+ else:
605
+ print(" - no data" if IS_EN else " - sem dados")
606
+ print()
607
+
608
+ print("Top actors (overall)" if IS_EN else "Top atores (geral)")
609
+ if actors_all:
610
+ for name, count in actors_all.most_common(10):
611
+ print(f" - {name}: {count}")
612
+ else:
613
+ print(" - no data" if IS_EN else " - sem dados")
614
+ print()
615
+
616
+ print("Last 24h" if IS_EN else "Ultimas 24h")
617
+ print(f" - {'completed total' if IS_EN else 'total finalizadas'}: {sum(tools_24h.values())}")
618
+ print(f" - status: {dict(status_24h) if status_24h else ({'no_data': 0} if IS_EN else {'sem_dados': 0})}")
619
+
620
+ print(" - top tools (24h)" if IS_EN else " - top ferramentas (24h)")
621
+ if tools_24h:
622
+ for name, count in tools_24h.most_common(10):
623
+ print(f" * {name}: {count}")
624
+ else:
625
+ print(" * no data" if IS_EN else " * sem dados")
626
+
627
+ print(" - top actors (24h)" if IS_EN else " - top atores (24h)")
628
+ if actors_24h:
629
+ for name, count in actors_24h.most_common(10):
630
+ print(f" * {name}: {count}")
631
+ else:
632
+ print(" * no data" if IS_EN else " * sem dados")
633
+ PYEOF
634
+ }
635
+
636
+ # ---------------------------------------------------------------------------
637
+ # Modo 7: full — painel completo
638
+ # ---------------------------------------------------------------------------
639
+ cmd_full() {
640
+ ensure_venv
641
+
642
+ echo -e "${DIM} $(t panel_updates) ${REFRESH_INTERVAL}s — Ctrl+C${NC}"
643
+ echo ""
644
+ sleep 1
645
+
646
+ while true; do
647
+ print_header
648
+
649
+ # Status do container
650
+ echo -e " ${BOLD}$(t container_docker)${NC}"
651
+ if command -v docker &>/dev/null && docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then
652
+ UPTIME=$(docker inspect --format='{{.State.StartedAt}}' "$CONTAINER_NAME" 2>/dev/null | cut -c1-19 | tr 'T' ' ')
653
+ echo -e " ${GREEN}●${NC} ${CONTAINER_NAME} $(t running) ${DIM}($(t started): ${UPTIME}Z)${NC}"
654
+ else
655
+ echo -e " ${RED}●${NC} ${CONTAINER_NAME} $(t offline)"
656
+ fi
657
+ echo ""
658
+
659
+ # Tamanho em disco
660
+ echo -e " ${BOLD}$(t disk_db)${NC}"
661
+ if [[ -d "$RAG_DB_DIR" ]]; then
662
+ DISK_SIZE=$(du -sh "$RAG_DB_DIR" 2>/dev/null | cut -f1)
663
+ DISK_FILES=$(find "$RAG_DB_DIR" -type f 2>/dev/null | wc -l)
664
+ echo -e " ${CYAN}${DISK_SIZE}${NC} ${DIM}(${DISK_FILES} $(t internal_files) ${RAG_DB_DIR})${NC}"
665
+ else
666
+ echo -e "${YELLOW}$(t dir_not_found) ${RAG_DB_DIR}${NC}"
667
+ fi
668
+ echo ""
669
+
670
+ # Coleções e chunks
671
+ echo -e " ${BOLD}$(t collections_chunks)${NC}"
672
+ MONITOR_LANG="${UI_LANG}" "${VENV_PYTHON}" - 2>/dev/null << 'PYEOF'
673
+ import chromadb, os, sys
674
+
675
+ GREEN = "\033[0;32m"; CYAN = "\033[0;36m"
676
+ YELLOW = "\033[1;33m"; RED = "\033[0;31m"
677
+ BOLD = "\033[1m"; DIM = "\033[2m"; NC = "\033[0m"
678
+ LANG = os.getenv("MONITOR_LANG", "pt-br").lower()
679
+ IS_EN = LANG == "en-us"
680
+
681
+ try:
682
+ client = chromadb.HttpClient(host="localhost", port=8000)
683
+ client.heartbeat()
684
+ except Exception as e:
685
+ msg = "ChromaDB unreachable" if IS_EN else "ChromaDB inacessível"
686
+ print(f" {RED}{msg}: {e}{NC}")
687
+ sys.exit(0)
688
+
689
+ collections = client.list_collections()
690
+
691
+ if not collections:
692
+ if IS_EN:
693
+ print(f" {YELLOW}Database empty — no collections found.{NC}")
694
+ print(" Run: indexer_full.py")
695
+ else:
696
+ print(f" {YELLOW}Banco vazio — nenhuma coleção encontrada.{NC}")
697
+ print(" Execute: indexer_full.py")
698
+ sys.exit(0)
699
+
700
+ total_chunks = 0
701
+ total_files = 0
702
+
703
+ for col in collections:
704
+ count = col.count()
705
+ total_chunks += count
706
+
707
+ all_meta = col.get(include=["metadatas"])
708
+ unique_files = {m["file_path"] for m in all_meta["metadatas"] if m and "file_path" in m}
709
+ total_files += len(unique_files)
710
+
711
+ max_count = max(c.count() for c in collections) or 1
712
+ bar_len = min(30, int(count / max_count * 30))
713
+ bar = "█" * bar_len + "░" * (30 - bar_len)
714
+
715
+ print(f" {GREEN}{BOLD}{col.name}{NC}")
716
+ print(f" {CYAN}{bar}{NC} {BOLD}{count:,}{NC} chunks | {len(unique_files):,} {'files' if IS_EN else 'arquivos'}")
717
+ print()
718
+
719
+ print(f" {'─'*55}")
720
+ if IS_EN:
721
+ print(f" {BOLD}Total: {CYAN}{total_chunks:,} chunks{NC}{BOLD} in {total_files:,} file(s){NC}")
722
+ else:
723
+ print(f" {BOLD}Total: {CYAN}{total_chunks:,} chunks{NC}{BOLD} em {total_files:,} arquivo(s){NC}")
724
+ PYEOF
725
+
726
+ echo ""
727
+ echo -e " ${DIM}$(t last_update): $(date '+%H:%M:%S') — $(t next_in) ${REFRESH_INTERVAL}s${NC}"
728
+
729
+ sleep "$REFRESH_INTERVAL"
730
+ done
731
+ }
732
+
733
+ # ---------------------------------------------------------------------------
734
+ # Modo 8: reset — remove todas as coleções do ChromaDB
735
+ # ---------------------------------------------------------------------------
736
+ cmd_reset() {
737
+ ensure_venv
738
+
739
+ echo ""
740
+ echo -e "${RED}${BOLD}$(t reset_attention)${NC}"
741
+ echo -e "${YELLOW}$(t reset_irreversible)${NC}"
742
+ echo ""
743
+
744
+ read -rp "$(t confirm_continue) ${YES_NO_HINT}: " confirm_reset
745
+ confirm_reset="$(echo "${confirm_reset:-}" | tr '[:upper:]' '[:lower:]')"
746
+ if [[ "$confirm_reset" != "s" && "$confirm_reset" != "sim" && "$confirm_reset" != "y" && "$confirm_reset" != "yes" ]]; then
747
+ log_info "$(t reset_cancelled)"
748
+ exit 0
749
+ fi
750
+
751
+ local reset_keyword="ZERAR"
752
+ if [[ "$UI_LANG" == "en-us" ]]; then
753
+ reset_keyword="RESET"
754
+ fi
755
+
756
+ read -rp "$(t confirm_type_reset) " final_confirmation
757
+ if [[ "${final_confirmation:-}" != "${reset_keyword}" ]]; then
758
+ log_info "$(t reset_cancelled)"
759
+ exit 0
760
+ fi
761
+
762
+ MONITOR_LANG="${UI_LANG}" "${VENV_PYTHON}" - << 'PYEOF'
763
+ import os
764
+ import sys
765
+ import chromadb
766
+
767
+ CHROMA_HOST = "localhost"
768
+ CHROMA_PORT = 8000
769
+ LANG = os.getenv("MONITOR_LANG", "pt-br").lower()
770
+ IS_EN = LANG == "en-us"
771
+
772
+ try:
773
+ client = chromadb.HttpClient(host=CHROMA_HOST, port=CHROMA_PORT)
774
+ client.heartbeat()
775
+ except Exception as e:
776
+ if IS_EN:
777
+ print(f"[ERROR] Could not connect to ChromaDB at {CHROMA_HOST}:{CHROMA_PORT}: {e}")
778
+ else:
779
+ print(f"[ERRO] Não foi possível conectar ao ChromaDB em {CHROMA_HOST}:{CHROMA_PORT}: {e}")
780
+ sys.exit(1)
781
+
782
+ collections = client.list_collections()
783
+ removed = 0
784
+ for col in collections:
785
+ client.delete_collection(name=col.name)
786
+ removed += 1
787
+
788
+ if IS_EN:
789
+ print(f"[+] Reset completed. Collections removed: {removed}")
790
+ else:
791
+ print(f"[+] Reset concluído. Coleções removidas: {removed}")
792
+ PYEOF
793
+ }
794
+
795
+ # ---------------------------------------------------------------------------
796
+ # Menu interativo
797
+ # ---------------------------------------------------------------------------
798
+ cmd_menu() {
799
+ ensure_venv
800
+ print_header
801
+
802
+ if is_chroma_running; then
803
+ echo -e " ${GREEN}${BOLD}$(t status_online)${NC}"
804
+ else
805
+ echo -e " ${RED}${BOLD}$(t status_offline)${NC}"
806
+ echo -e " ${DIM}$(t start_with): docker compose -f ~/docker-chromadb/docker-compose.yml up -d${NC}"
807
+ fi
808
+ echo ""
809
+ echo -e " ${BOLD}$(t choose_option)${NC}"
810
+ echo ""
811
+ echo -e " ${CYAN}$(t menu_1)${NC}"
812
+ echo -e " ${CYAN}$(t menu_2)${NC}"
813
+ echo -e " ${CYAN}$(t menu_3)${NC}"
814
+ echo -e " ${CYAN}$(t menu_4)${NC}"
815
+ echo -e " ${CYAN}$(t menu_5)${NC}"
816
+ echo -e " ${CYAN}$(t menu_6)${NC}"
817
+ echo -e " ${CYAN}$(t menu_7)${NC}"
818
+ echo -e " ${CYAN}$(t menu_8)${NC}"
819
+ echo ""
820
+ echo -e " ${CYAN}$(t menu_0)${NC}"
821
+ echo ""
822
+ read -rp " $(t option_prompt) " choice
823
+
824
+ case "$choice" in
825
+ 1) echo ""; cmd_chunks ;;
826
+ 2) echo ""; cmd_watch ;;
827
+ 3) cmd_disk ;;
828
+ 4) cmd_logs ;;
829
+ 5) cmd_mcp_logs ;;
830
+ 6) cmd_mcp_summary ;;
831
+ 7) cmd_full ;;
832
+ 8) cmd_reset ;;
833
+ 0) exit 0 ;;
834
+ *) echo -e "\n ${YELLOW}$(t invalid_option)${NC}"; sleep 1; cmd_menu ;;
835
+ esac
836
+ }
837
+
838
+ # ---------------------------------------------------------------------------
839
+ # Dispatcher
840
+ # ---------------------------------------------------------------------------
841
+ select_ui_language
842
+ case "${1:-menu}" in
843
+ chunks) cmd_chunks ;;
844
+ watch) cmd_watch ;;
845
+ disk) cmd_disk ;;
846
+ logs) cmd_logs ;;
847
+ mcp-logs) cmd_mcp_logs ;;
848
+ mcp-summary) cmd_mcp_summary ;;
849
+ full) cmd_full ;;
850
+ reset) cmd_reset ;;
851
+ menu) cmd_menu ;;
852
+ *)
853
+ echo -e "${RED}[$(t err_prefix)]${NC} $(t unknown_mode): '$1'"
854
+ echo "$(t usage)"
855
+ exit 1
856
+ ;;
857
+ esac