vibe/vibe/cli/cli.py
Quentin 5d2e01a6d7
v2.3.0 (#429)
Co-authored-by: Carlo <carloantonio.patti@mistral.ai>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Clement Sirieix <clem.sirieix@gmail.com>
Co-authored-by: Michel Thomazo <michel.thomazo@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-authored-by: Thomas Kenbeek <thomas.kenbeek@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-02-27 17:31:58 +01:00

207 lines
6.6 KiB
Python

from __future__ import annotations
import argparse
from pathlib import Path
import sys
from rich import print as rprint
from vibe import __version__
from vibe.cli.textual_ui.app import run_textual_ui
from vibe.core.agent_loop import AgentLoop
from vibe.core.agents.models import BuiltinAgentName
from vibe.core.config import (
MissingAPIKeyError,
MissingPromptFileError,
VibeConfig,
load_dotenv_values,
)
from vibe.core.logger import logger
from vibe.core.paths.config_paths import CONFIG_FILE, HISTORY_FILE
from vibe.core.programmatic import run_programmatic
from vibe.core.session.session_loader import SessionLoader
from vibe.core.types import EntrypointMetadata, LLMMessage, OutputFormat, Role
from vibe.core.utils import ConversationLimitException
from vibe.setup.onboarding import run_onboarding
def get_initial_agent_name(args: argparse.Namespace) -> str:
if args.prompt is not None and args.agent == BuiltinAgentName.DEFAULT:
return BuiltinAgentName.AUTO_APPROVE
return args.agent
def get_prompt_from_stdin() -> str | None:
if sys.stdin.isatty():
return None
try:
if content := sys.stdin.read().strip():
sys.stdin = sys.__stdin__ = open("/dev/tty")
return content
except KeyboardInterrupt:
pass
except OSError:
return None
return None
def load_config_or_exit() -> VibeConfig:
try:
return VibeConfig.load()
except MissingAPIKeyError:
run_onboarding()
return VibeConfig.load()
except MissingPromptFileError as e:
rprint(f"[yellow]Invalid system prompt id: {e}[/]")
sys.exit(1)
except ValueError as e:
rprint(f"[yellow]{e}[/]")
sys.exit(1)
def bootstrap_config_files() -> None:
if not CONFIG_FILE.path.exists():
try:
VibeConfig.save_updates(VibeConfig.create_default())
except Exception as e:
rprint(f"[yellow]Could not create default config file: {e}[/]")
if not HISTORY_FILE.path.exists():
try:
HISTORY_FILE.path.parent.mkdir(parents=True, exist_ok=True)
HISTORY_FILE.path.write_text("Hello Vibe!\n", "utf-8")
except Exception as e:
rprint(f"[yellow]Could not create history file: {e}[/]")
def load_session(
args: argparse.Namespace, config: VibeConfig
) -> tuple[list[LLMMessage], Path] | None:
if not args.continue_session and not args.resume:
return None
if not config.session_logging.enabled:
rprint(
"[red]Session logging is disabled. "
"Enable it in config to use --continue or --resume[/]"
)
sys.exit(1)
session_to_load = None
if args.continue_session:
session_to_load = SessionLoader.find_latest_session(config.session_logging)
if not session_to_load:
rprint(
f"[red]No previous sessions found in "
f"{config.session_logging.save_dir}[/]"
)
sys.exit(1)
else:
session_to_load = SessionLoader.find_session_by_id(
args.resume, config.session_logging
)
if not session_to_load:
rprint(
f"[red]Session '{args.resume}' not found in "
f"{config.session_logging.save_dir}[/]"
)
sys.exit(1)
try:
loaded_messages, _ = SessionLoader.load_session(session_to_load)
return loaded_messages, session_to_load
except Exception as e:
rprint(f"[red]Failed to load session: {e}[/]")
sys.exit(1)
def _resume_previous_session(
agent_loop: AgentLoop, loaded_messages: list[LLMMessage], session_path: Path
) -> None:
non_system_messages = [msg for msg in loaded_messages if msg.role != Role.system]
agent_loop.messages.extend(non_system_messages)
_, metadata = SessionLoader.load_session(session_path)
session_id = metadata.get("session_id", agent_loop.session_id)
agent_loop.session_id = session_id
agent_loop.session_logger.resume_existing_session(session_id, session_path)
logger.info(
"Resumed session %s with %d messages", session_id, len(non_system_messages)
)
def run_cli(args: argparse.Namespace) -> None:
load_dotenv_values()
bootstrap_config_files()
if args.setup:
run_onboarding()
sys.exit(0)
try:
initial_agent_name = get_initial_agent_name(args)
config = load_config_or_exit()
if args.enabled_tools:
config.enabled_tools = args.enabled_tools
loaded_session = load_session(args, config)
stdin_prompt = get_prompt_from_stdin()
if args.prompt is not None:
programmatic_prompt = args.prompt or stdin_prompt
if not programmatic_prompt:
print(
"Error: No prompt provided for programmatic mode", file=sys.stderr
)
sys.exit(1)
output_format = OutputFormat(
args.output if hasattr(args, "output") else "text"
)
try:
final_response = run_programmatic(
config=config,
prompt=programmatic_prompt,
max_turns=args.max_turns,
max_price=args.max_price,
output_format=output_format,
previous_messages=loaded_session[0] if loaded_session else None,
agent_name=initial_agent_name,
)
if final_response:
print(final_response)
sys.exit(0)
except ConversationLimitException as e:
print(e, file=sys.stderr)
sys.exit(1)
except RuntimeError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
else:
agent_loop = AgentLoop(
config,
agent_name=initial_agent_name,
enable_streaming=True,
entrypoint_metadata=EntrypointMetadata(
agent_entrypoint="cli",
agent_version=__version__,
client_name="vibe_cli",
client_version=__version__,
),
)
if loaded_session:
_resume_previous_session(agent_loop, *loaded_session)
run_textual_ui(
agent_loop=agent_loop,
initial_prompt=args.initial_prompt or stdin_prompt,
teleport_on_start=args.teleport,
)
except (KeyboardInterrupt, EOFError):
rprint("\n[dim]Bye![/]")
sys.exit(0)