gemini-cli-pro 0.0.4-snapshot → 0.0.7-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.
- package/README.md +1 -1
- package/bin/gemini +19 -1
- package/gemini_tool.py +132 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,5 +52,5 @@ Comandos do chat:
|
|
|
52
52
|
|
|
53
53
|
O arquivo `~/.cache/gemini-history-chats/config.json` controla os MCPs (`local_rag`, `github`, `memory`).
|
|
54
54
|
Se algum MCP nao estiver disponivel, o CLI continua funcionando no modo API.
|
|
55
|
-
O MCP local de RAG usa `
|
|
55
|
+
O MCP local de RAG usa `rag-codebase` via comando `mcp-rag-server` quando estiver instalado.
|
|
56
56
|
Em toda nova sessao, os MCPs sao recarregados automaticamente no startup.
|
package/bin/gemini
CHANGED
|
@@ -11,6 +11,18 @@ done
|
|
|
11
11
|
SCRIPT_DIR="$(cd -P "$(dirname "${SOURCE}")" && pwd)"
|
|
12
12
|
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
13
13
|
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
|
14
|
+
TOOL_PATH="${PROJECT_DIR}/gemini_tool.py"
|
|
15
|
+
|
|
16
|
+
if [ ! -f "${TOOL_PATH}" ] && command -v npm >/dev/null 2>&1; then
|
|
17
|
+
NPM_ROOT="$(npm root -g 2>/dev/null || true)"
|
|
18
|
+
for PKG_NAME in "gemini-cli-pro" "gemini-cli" "@jocsapb/gemini-cli"; do
|
|
19
|
+
CANDIDATE="${NPM_ROOT}/${PKG_NAME}/gemini_tool.py"
|
|
20
|
+
if [ -f "${CANDIDATE}" ]; then
|
|
21
|
+
TOOL_PATH="${CANDIDATE}"
|
|
22
|
+
break
|
|
23
|
+
fi
|
|
24
|
+
done
|
|
25
|
+
fi
|
|
14
26
|
|
|
15
27
|
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
|
|
16
28
|
echo "Erro: python3 nao encontrado no PATH." >&2
|
|
@@ -23,4 +35,10 @@ if ! "${PYTHON_BIN}" -c "import google.generativeai" >/dev/null 2>&1; then
|
|
|
23
35
|
exit 1
|
|
24
36
|
fi
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
if [ ! -f "${TOOL_PATH}" ]; then
|
|
39
|
+
echo "Erro: gemini_tool.py nao encontrado (procurei em ${TOOL_PATH})." >&2
|
|
40
|
+
echo "Reinstale com: npm install -g ." >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
exec "${PYTHON_BIN}" "${TOOL_PATH}" "$@"
|
package/gemini_tool.py
CHANGED
|
@@ -21,8 +21,21 @@ MEMORY_PATH = CACHE_DIR / "memory_store.json"
|
|
|
21
21
|
|
|
22
22
|
MODEL_FLASH = "gemini-2.5-flash"
|
|
23
23
|
MODEL_PRO = "gemini-2.5-pro"
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
RAG_SERVER_COMMAND = "mcp-rag-server"
|
|
25
|
+
|
|
26
|
+
ANSI_RESET = "\033[0m"
|
|
27
|
+
ANSI_BOLD = "\033[1m"
|
|
28
|
+
ANSI_DIM = "\033[2m"
|
|
29
|
+
ANSI_CYAN = "\033[96m"
|
|
30
|
+
ANSI_BLUE = "\033[94m"
|
|
31
|
+
ANSI_GREEN = "\033[92m"
|
|
32
|
+
ANSI_YELLOW = "\033[93m"
|
|
33
|
+
ANSI_RED = "\033[91m"
|
|
34
|
+
ANSI_MAGENTA = "\033[95m"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def colorize(text: str, color: str) -> str:
|
|
38
|
+
return f"{color}{text}{ANSI_RESET}"
|
|
26
39
|
|
|
27
40
|
|
|
28
41
|
def ensure_cache_layout() -> None:
|
|
@@ -34,9 +47,9 @@ def ensure_cache_layout() -> None:
|
|
|
34
47
|
"mcps": {
|
|
35
48
|
"local_rag": {
|
|
36
49
|
"enabled": True,
|
|
37
|
-
"transport": "
|
|
38
|
-
"
|
|
39
|
-
"description": "Local RAG via
|
|
50
|
+
"transport": "mcp-stdio",
|
|
51
|
+
"server_command": RAG_SERVER_COMMAND,
|
|
52
|
+
"description": "Local RAG via servidor MCP rag-codebase",
|
|
40
53
|
},
|
|
41
54
|
"github": {
|
|
42
55
|
"enabled": True,
|
|
@@ -85,7 +98,7 @@ def merge_dict(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]
|
|
|
85
98
|
def require_api_key() -> str:
|
|
86
99
|
api_key = os.getenv("GOOGLE_API_KEY")
|
|
87
100
|
if not api_key:
|
|
88
|
-
print("Erro: defina GOOGLE_API_KEY antes de iniciar.", file=sys.stderr)
|
|
101
|
+
print(colorize("Erro: defina GOOGLE_API_KEY antes de iniciar.", ANSI_RED), file=sys.stderr)
|
|
89
102
|
sys.exit(1)
|
|
90
103
|
return api_key
|
|
91
104
|
|
|
@@ -200,67 +213,103 @@ def emit_plin() -> None:
|
|
|
200
213
|
print("\a", end="", flush=True)
|
|
201
214
|
|
|
202
215
|
|
|
203
|
-
def
|
|
204
|
-
candidates: list[str] = [
|
|
216
|
+
def _config_rag_server_candidates(config: dict[str, Any]) -> list[str]:
|
|
217
|
+
candidates: list[str] = [RAG_SERVER_COMMAND]
|
|
205
218
|
mcps = config.get("mcps", {})
|
|
206
219
|
if isinstance(mcps, dict):
|
|
207
220
|
local_rag = mcps.get("local_rag", {})
|
|
208
221
|
if isinstance(local_rag, dict):
|
|
209
|
-
configured = local_rag.get("
|
|
222
|
+
configured = local_rag.get("server_command")
|
|
210
223
|
if isinstance(configured, str) and configured.strip():
|
|
211
|
-
candidates.
|
|
224
|
+
candidates.insert(0, configured.strip())
|
|
212
225
|
if isinstance(configured, list):
|
|
213
226
|
for item in configured:
|
|
214
227
|
if isinstance(item, str) and item.strip():
|
|
215
|
-
candidates.
|
|
216
|
-
candidates.append(RAG_FALLBACK_COMMAND)
|
|
217
|
-
# remove duplicados preservando ordem
|
|
228
|
+
candidates.insert(0, item.strip())
|
|
218
229
|
return list(dict.fromkeys(candidates))
|
|
219
230
|
|
|
220
231
|
|
|
221
|
-
def
|
|
232
|
+
def resolve_rag_server_command(config: dict[str, Any] | None = None) -> str | None:
|
|
222
233
|
effective_config = config if isinstance(config, dict) else load_config()
|
|
223
|
-
for candidate in
|
|
234
|
+
for candidate in _config_rag_server_candidates(effective_config):
|
|
224
235
|
if shutil.which(candidate):
|
|
225
236
|
return candidate
|
|
226
237
|
return None
|
|
227
238
|
|
|
228
239
|
|
|
229
|
-
def
|
|
230
|
-
return
|
|
240
|
+
def check_rag_server_available(config: dict[str, Any] | None = None) -> bool:
|
|
241
|
+
return resolve_rag_server_command(config) is not None
|
|
231
242
|
|
|
232
243
|
|
|
233
244
|
def rag_sync_current_dir(config: dict[str, Any] | None = None) -> tuple[bool, str]:
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return
|
|
245
|
+
if resolve_rag_server_command(config):
|
|
246
|
+
# Com MCP rag-codebase disponível, considera backend RAG pronto.
|
|
247
|
+
return True, "OK"
|
|
248
|
+
return False, "OFF"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _python_for_script(script_path: str) -> str:
|
|
252
|
+
try:
|
|
253
|
+
with open(script_path, "r", encoding="utf-8") as handle:
|
|
254
|
+
first_line = handle.readline().strip()
|
|
255
|
+
if first_line.startswith("#!") and "python" in first_line:
|
|
256
|
+
return first_line[2:].strip()
|
|
257
|
+
except Exception:
|
|
258
|
+
pass
|
|
259
|
+
return sys.executable
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def search_local_context_via_rag_server(query: str) -> str:
|
|
263
|
+
server_command = resolve_rag_server_command()
|
|
264
|
+
if not server_command:
|
|
265
|
+
return ""
|
|
266
|
+
|
|
267
|
+
server_path = shutil.which(server_command)
|
|
268
|
+
if not server_path:
|
|
269
|
+
return ""
|
|
270
|
+
|
|
271
|
+
runner_code = r'''
|
|
272
|
+
import importlib.util
|
|
273
|
+
import pathlib
|
|
274
|
+
import sys
|
|
275
|
+
|
|
276
|
+
server_file = pathlib.Path(sys.argv[1]).expanduser().resolve()
|
|
277
|
+
query = sys.argv[2]
|
|
278
|
+
top_k = int(sys.argv[3])
|
|
279
|
+
mode = sys.argv[4]
|
|
280
|
+
|
|
281
|
+
spec = importlib.util.spec_from_file_location("mcp_rag_server_runtime", server_file)
|
|
282
|
+
if spec is None or spec.loader is None:
|
|
283
|
+
raise RuntimeError("falha ao carregar modulo do mcp-rag-server")
|
|
284
|
+
mod = importlib.util.module_from_spec(spec)
|
|
285
|
+
spec.loader.exec_module(mod)
|
|
237
286
|
|
|
238
|
-
|
|
287
|
+
if not hasattr(mod, "semantic_search_code"):
|
|
288
|
+
raise RuntimeError("semantic_search_code nao encontrado no mcp-rag-server")
|
|
289
|
+
|
|
290
|
+
result = mod.semantic_search_code(query=query, top_k=top_k, mode=mode)
|
|
291
|
+
print(result if isinstance(result, str) else str(result))
|
|
292
|
+
'''
|
|
293
|
+
|
|
294
|
+
py_exec = _python_for_script(server_path)
|
|
295
|
+
result = _run_command([py_exec, "-c", runner_code, server_path, query, "6", "single"], timeout=180)
|
|
296
|
+
stdout = (result.stdout or "").strip()
|
|
297
|
+
stderr = (result.stderr or "").strip()
|
|
239
298
|
if result.returncode == 0:
|
|
240
|
-
return
|
|
241
|
-
return
|
|
299
|
+
return stdout or "Nenhum resultado no rag-codebase."
|
|
300
|
+
return f"Erro no rag-codebase (code={result.returncode}): {stderr or stdout or 'sem detalhes'}"
|
|
242
301
|
|
|
243
302
|
|
|
244
303
|
def search_local_context(query: str) -> str:
|
|
245
|
-
"""Busca no contexto local usando
|
|
246
|
-
|
|
247
|
-
if not
|
|
248
|
-
return "
|
|
304
|
+
"""Busca no contexto local usando o servidor MCP rag-codebase."""
|
|
305
|
+
server_command = resolve_rag_server_command()
|
|
306
|
+
if not server_command:
|
|
307
|
+
return ""
|
|
249
308
|
|
|
250
309
|
query = (query or "").strip()
|
|
251
310
|
if not query:
|
|
252
311
|
return "Consulta vazia."
|
|
253
|
-
|
|
254
|
-
result = _run_command([rag_command, "search", query])
|
|
255
|
-
stdout = (result.stdout or "").strip()
|
|
256
|
-
stderr = (result.stderr or "").strip()
|
|
257
|
-
|
|
258
|
-
if result.returncode == 0:
|
|
259
|
-
return stdout or "Nenhum resultado no RAG local."
|
|
260
|
-
return (
|
|
261
|
-
f"Erro no {rag_command} search (code={result.returncode}): "
|
|
262
|
-
f"{stderr or stdout or 'sem detalhes'}"
|
|
263
|
-
)
|
|
312
|
+
return search_local_context_via_rag_server(query)
|
|
264
313
|
|
|
265
314
|
|
|
266
315
|
def github_tool(action: str, repo: str, path: str = "") -> str:
|
|
@@ -356,7 +405,7 @@ def _mcp_enabled(config: dict[str, Any], mcp_name: str, default: bool = True) ->
|
|
|
356
405
|
|
|
357
406
|
def build_tools(config: dict[str, Any]) -> list[Any]:
|
|
358
407
|
tools: list[Any] = []
|
|
359
|
-
if _mcp_enabled(config, "local_rag", default=True) and
|
|
408
|
+
if _mcp_enabled(config, "local_rag", default=True) and check_rag_server_available(config):
|
|
360
409
|
tools.append(as_executable(search_local_context))
|
|
361
410
|
if _mcp_enabled(config, "github", default=True):
|
|
362
411
|
tools.append(as_executable(github_tool))
|
|
@@ -383,6 +432,7 @@ class GeminiToolCLI:
|
|
|
383
432
|
self.tools_active = False
|
|
384
433
|
self.tools_error = ""
|
|
385
434
|
self.rag_command: str | None = None
|
|
435
|
+
self.rag_server_found = False
|
|
386
436
|
self.rag_ok = False
|
|
387
437
|
self.rag_status = "MISSING"
|
|
388
438
|
|
|
@@ -391,6 +441,18 @@ class GeminiToolCLI:
|
|
|
391
441
|
self.model = self._build_model(self.current_model_name)
|
|
392
442
|
self.chat = self.model.start_chat(history=self.history)
|
|
393
443
|
|
|
444
|
+
def _print_info(self, message: str) -> None:
|
|
445
|
+
print(colorize(message, ANSI_BLUE))
|
|
446
|
+
|
|
447
|
+
def _print_success(self, message: str) -> None:
|
|
448
|
+
print(colorize(message, ANSI_GREEN))
|
|
449
|
+
|
|
450
|
+
def _print_warn(self, message: str) -> None:
|
|
451
|
+
print(colorize(message, ANSI_YELLOW))
|
|
452
|
+
|
|
453
|
+
def _print_error(self, message: str) -> None:
|
|
454
|
+
print(colorize(message, ANSI_RED))
|
|
455
|
+
|
|
394
456
|
def _build_model(self, model_name: str) -> genai.GenerativeModel:
|
|
395
457
|
if not self.tools:
|
|
396
458
|
return genai.GenerativeModel(model_name=model_name)
|
|
@@ -408,7 +470,10 @@ class GeminiToolCLI:
|
|
|
408
470
|
return "FLASH"
|
|
409
471
|
|
|
410
472
|
def _prompt(self) -> str:
|
|
411
|
-
|
|
473
|
+
model_tag = colorize(self._prompt_model_tag(), ANSI_MAGENTA + ANSI_BOLD)
|
|
474
|
+
rag_tag = colorize(self.rag_status, ANSI_GREEN if self.rag_status == "OK" else ANSI_YELLOW)
|
|
475
|
+
arrow = f"{ANSI_CYAN}{ANSI_BOLD}>> {ANSI_CYAN}"
|
|
476
|
+
return f"[{model_tag}][RAG:{rag_tag}] {arrow}"
|
|
412
477
|
|
|
413
478
|
def _switch_model_if_needed(self, model_name: str) -> None:
|
|
414
479
|
if model_name == self.current_model_name:
|
|
@@ -480,28 +545,36 @@ class GeminiToolCLI:
|
|
|
480
545
|
self.history.append({"role": role, "parts": [text]})
|
|
481
546
|
|
|
482
547
|
def _print_help(self) -> None:
|
|
483
|
-
|
|
548
|
+
self._print_info("Comandos: /auto, /pro, /flash, /sync, /reload-mcps, /status, /help, /exit")
|
|
484
549
|
|
|
485
550
|
def _show_status(self) -> None:
|
|
486
551
|
tools_state = "ON" if self.tools_active else "OFF"
|
|
487
552
|
rag_cmd = self.rag_command or "none"
|
|
488
553
|
print(
|
|
489
|
-
|
|
490
|
-
|
|
554
|
+
colorize(
|
|
555
|
+
f"mode={self.mode} model={self._prompt_model_tag()} rag={self.rag_status} "
|
|
556
|
+
f"rag_cmd={rag_cmd} tools={tools_state} history={history_path_for_cwd()}",
|
|
557
|
+
ANSI_DIM,
|
|
558
|
+
)
|
|
491
559
|
)
|
|
492
560
|
if self.tools_error:
|
|
493
|
-
|
|
561
|
+
self._print_warn(f"tools_error={self.tools_error}")
|
|
494
562
|
|
|
495
563
|
def _resync_rag(self) -> None:
|
|
496
564
|
rag_ok, rag_status = rag_sync_current_dir(self.config)
|
|
497
565
|
self.rag_ok = rag_ok
|
|
498
566
|
self.rag_status = rag_status
|
|
499
|
-
|
|
567
|
+
if self.rag_ok:
|
|
568
|
+
self._print_success(f"RAG sync status: {self.rag_status}")
|
|
569
|
+
else:
|
|
570
|
+
self._print_warn(f"RAG sync status: {self.rag_status}")
|
|
500
571
|
|
|
501
572
|
def reload_mcps(self, sync_rag: bool = True, announce: bool = True) -> None:
|
|
502
573
|
"""Recarrega MCPs/config no inicio da sessao e sob comando manual."""
|
|
503
574
|
self.config = load_config()
|
|
504
|
-
|
|
575
|
+
rag_server_command = resolve_rag_server_command(self.config)
|
|
576
|
+
self.rag_command = rag_server_command
|
|
577
|
+
self.rag_server_found = rag_server_command is not None
|
|
505
578
|
self.tools = build_tools(self.config)
|
|
506
579
|
self.tools_active = bool(self.tools)
|
|
507
580
|
self.tools_error = ""
|
|
@@ -518,31 +591,28 @@ class GeminiToolCLI:
|
|
|
518
591
|
|
|
519
592
|
if announce:
|
|
520
593
|
rag_info = self.rag_command or "indisponivel"
|
|
521
|
-
|
|
594
|
+
self._print_info(
|
|
522
595
|
f"MCPs recarregados. tools={'ON' if self.tools_active else 'OFF'} "
|
|
523
596
|
f"rag={self.rag_status} cmd={rag_info}"
|
|
524
597
|
)
|
|
525
598
|
|
|
526
599
|
def run(self) -> int:
|
|
527
|
-
|
|
528
|
-
print(f"Historico: {history_path_for_cwd()}")
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
print(
|
|
532
|
-
"Aviso: RAG nao esta OK. Use /sync para tentar novamente ",
|
|
533
|
-
"(ou instale own-rag-cli/own-rag).",
|
|
534
|
-
)
|
|
600
|
+
self._print_info(f"{ANSI_BOLD}Gemini Tool iniciado em:{ANSI_RESET} {os.path.abspath(os.getcwd())}")
|
|
601
|
+
print(colorize(f"Historico: {history_path_for_cwd()}", ANSI_DIM))
|
|
602
|
+
if self.rag_server_found:
|
|
603
|
+
self._print_success("Servidor rag-codebase encontrado. Vou utiliza-lo nesta sessao.")
|
|
535
604
|
|
|
536
605
|
self._print_help()
|
|
537
606
|
|
|
538
607
|
while True:
|
|
539
608
|
try:
|
|
540
609
|
raw = input(self._prompt()).strip()
|
|
610
|
+
print(ANSI_RESET, end="")
|
|
541
611
|
except EOFError:
|
|
542
612
|
print()
|
|
543
613
|
break
|
|
544
614
|
except KeyboardInterrupt:
|
|
545
|
-
|
|
615
|
+
self._print_warn("Interrompido.")
|
|
546
616
|
break
|
|
547
617
|
|
|
548
618
|
if not raw:
|
|
@@ -564,17 +634,17 @@ class GeminiToolCLI:
|
|
|
564
634
|
continue
|
|
565
635
|
if raw == "/auto":
|
|
566
636
|
self.mode = "AUTO"
|
|
567
|
-
|
|
637
|
+
self._print_info("Modo alterado para AUTO")
|
|
568
638
|
continue
|
|
569
639
|
if raw == "/pro":
|
|
570
640
|
self.mode = "PRO"
|
|
571
641
|
self._switch_model_if_needed(MODEL_PRO)
|
|
572
|
-
|
|
642
|
+
self._print_info("Modo alterado para PRO")
|
|
573
643
|
continue
|
|
574
644
|
if raw == "/flash":
|
|
575
645
|
self.mode = "FLASH"
|
|
576
646
|
self._switch_model_if_needed(MODEL_FLASH)
|
|
577
|
-
|
|
647
|
+
self._print_info("Modo alterado para FLASH")
|
|
578
648
|
continue
|
|
579
649
|
|
|
580
650
|
decision = self.route_for_input(raw)
|
|
@@ -585,15 +655,16 @@ class GeminiToolCLI:
|
|
|
585
655
|
full_response = ""
|
|
586
656
|
try:
|
|
587
657
|
stream = self.chat.send_message(raw, stream=True)
|
|
658
|
+
print(ANSI_GREEN, end="")
|
|
588
659
|
for chunk in stream:
|
|
589
660
|
piece = self._extract_text(chunk)
|
|
590
661
|
if piece:
|
|
591
662
|
full_response += piece
|
|
592
663
|
print(piece, end="", flush=True)
|
|
593
|
-
print()
|
|
664
|
+
print(ANSI_RESET)
|
|
594
665
|
emit_plin()
|
|
595
666
|
except Exception as exc:
|
|
596
|
-
|
|
667
|
+
self._print_error(f"Erro ao gerar resposta: {exc}")
|
|
597
668
|
self.history.pop()
|
|
598
669
|
emit_plin()
|
|
599
670
|
continue
|