diff --git a/strix/interface/cli_args.py b/strix/interface/cli_args.py index c69ff959..2cbae624 100644 --- a/strix/interface/cli_args.py +++ b/strix/interface/cli_args.py @@ -77,8 +77,8 @@ def parse_arguments() -> argparse.Namespace: parser = argparse.ArgumentParser( description=t("cli.description"), formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: + epilog=f""" +{t("cli.examples_header")} # Web application penetration test strix --target https://example.com @@ -175,11 +175,7 @@ Examples: type=str, action="append", metavar="PATH[:DEST]", - help="Place a file from this machine into the sandbox workspace before the scan " - "starts, for example a wordlist, an API specification, or notes. Repeat the option " - "for more files. DEST is the path inside /workspace and defaults to the file name " - "(for example '--workspace-file ./wordlist.txt:lists/wordlist.txt'). The file is " - "read-only inside the sandbox and lands outside every target directory.", + help=t("cli.workspace_file_help"), ) parser.add_argument( diff --git a/strix/interface/main.py b/strix/interface/main.py index f272b00a..220134d4 100644 --- a/strix/interface/main.py +++ b/strix/interface/main.py @@ -156,17 +156,16 @@ async def warm_up_llm(show_model_warning: bool = True) -> None: and not llm.api_base ): warn_text = Text() - warn_text.append("UNKNOWN MODEL NAME", style="bold yellow") + warn_text.append(t("cli.unknown_model"), style="bold yellow") warn_text.append("\n\n", style="white") warn_text.append(f"'{raw_model}'", style="bold cyan") warn_text.append( - " is not a known OpenAI model. Bare names route to OpenAI by default.\n" - "If you meant a non-OpenAI provider, use the '", + t("cli.unknown_model_body"), style="white", ) warn_text.append("/", style="bold cyan") warn_text.append( - "' form, e.g. 'anthropic/claude-opus-4-7', 'deepseek/deepseek-v4-pro'.", + t("cli.unknown_model_hint"), style="white", ) console.print( @@ -182,18 +181,17 @@ async def warm_up_llm(show_model_warning: bool = True) -> None: if show_model_warning and raw_model and not is_recommended_or_frontier_model(raw_model): warn_text = Text() - warn_text.append("MODEL QUALITY WARNING", style="bold yellow") + warn_text.append(t("cli.model_quality_warning"), style="bold yellow") warn_text.append("\n\n", style="white") warn_text.append(f"'{raw_model}'", style="bold cyan") warn_text.append( - " is not a recommended frontier model for Strix.\nSecurity scans work best with:\n", + t("cli.model_quality_warning_body"), style="white", ) for recommended_model in RECOMMENDED_MODEL_NAMES: warn_text.append(f"• {recommended_model}\n", style="bold cyan") warn_text.append( - "\nYou can continue, but weaker models may miss vulnerabilities " - "or produce lower-quality findings.", + t("cli.model_quality_warning_footer"), style="white", ) console.print( diff --git a/strix/locales/en.json b/strix/locales/en.json index ce57271c..db10937a 100644 --- a/strix/locales/en.json +++ b/strix/locales/en.json @@ -13,6 +13,8 @@ "cli.max_turns_help": "Maximum turns per agent (> 0, default %(default)s). Each agent is force-stopped when it reaches this limit, with graduated wrap-up warnings as it is approached.", "cli.resume_help": "Resume a prior scan by its run name (the dir under ./strix_runs/). Picks up the root + every non-terminal subagent's full LLM history and agent topology. Skips fresh run-name generation.", "cli.language_help": "Language for UI and agent responses (e.g., 'en', 'es'). Default: auto-detect from environment.", + "cli.workspace_file_help": "Place a file from this machine into the sandbox workspace before the scan starts, for example a wordlist, an API specification, or notes. Repeat the option for more files. DEST is the path inside /workspace and defaults to the file name (for example '--workspace-file ./wordlist.txt:lists/wordlist.txt'). The file is read-only inside the sandbox and lands outside every target directory.", + "cli.examples_header": "Examples:", "cli.update_help": "Update strix to the latest version and exit. Self-updates the standalone binary install; for pip/pipx/uv installs, prints the matching upgrade command instead.", "cli.version_help": "Show version and exit.", "cli.error_no_target": "No target specified. Use --target or --target-list.", @@ -53,7 +55,11 @@ "cli.llm_check_config": "Please check your configuration and try again.", "cli.model_not_available": "MODEL NOT AVAILABLE ON SUBSCRIPTION", "cli.unknown_model": "UNKNOWN MODEL NAME", + "cli.unknown_model_body": " is not a known OpenAI model. Bare names route to OpenAI by default.\nIf you meant a non-OpenAI provider, use the '", + "cli.unknown_model_hint": "' form, e.g. 'anthropic/claude-opus-4-7', 'deepseek/deepseek-v4-pro'.", "cli.model_quality_warning": "MODEL QUALITY WARNING", + "cli.model_quality_warning_body": " is not a recommended frontier model for Strix.\nSecurity scans work best with:\n", + "cli.model_quality_warning_footer": "\nYou can continue, but weaker models may miss vulnerabilities or produce lower-quality findings.", "cli.interactive_setup_unavailable": "INTERACTIVE SETUP UNAVAILABLE", "cli.scan_preparation_failed": "SCAN PREPARATION FAILED", "cli.test_initiated": "Penetration test initiated", diff --git a/strix/locales/es.json b/strix/locales/es.json index 90615738..0f0fa4ce 100644 --- a/strix/locales/es.json +++ b/strix/locales/es.json @@ -13,6 +13,8 @@ "cli.max_turns_help": "Máximo de turnos por agente (> 0, por defecto %(default)s). Cada agente se detiene forzosamente al alcanzar este límite, con advertencias graduales al acercarse.", "cli.resume_help": "Reanudar un escaneo anterior por nombre de ejecución (el directorio bajo ./strix_runs/). Retoma el historial LLM completo del agente raíz y subagentes no terminados. Omite la generación de nombre nuevo.", "cli.language_help": "Idioma para la interfaz y respuestas de agentes (ej: 'en', 'es'). Por defecto: auto-detectar del entorno.", + "cli.workspace_file_help": "Coloca un archivo de esta máquina en el workspace del sandbox antes de que comience el escaneo, por ejemplo una wordlist, una especificación de API o notas. Repite la opción para más archivos. DEST es la ruta dentro de /workspace y por defecto usa el nombre del archivo (por ejemplo '--workspace-file ./wordlist.txt:lists/wordlist.txt'). El archivo es de solo lectura dentro del sandbox y queda fuera de cada directorio de objetivo.", + "cli.examples_header": "Ejemplos:", "cli.update_help": "Actualizar strix a la última versión y salir. Auto-actualiza la instalación binaria; para instalaciones pip/pipx/uv, muestra el comando de actualización correspondiente.", "cli.version_help": "Mostrar versión y salir.", "cli.error_no_target": "No se especificó objetivo. Use --target o --target-list.", @@ -53,7 +55,11 @@ "cli.llm_check_config": "Verifique su configuración e intente nuevamente.", "cli.model_not_available": "MODELO NO DISPONIBLE EN SUSCRIPCIÓN", "cli.unknown_model": "NOMBRE DE MODELO DESCONOCIDO", + "cli.unknown_model_body": " no es un modelo de OpenAI conocido. Los nombres simples se enrutan a OpenAI por defecto.\nSi te referías a un proveedor distinto de OpenAI, usa '", + "cli.unknown_model_hint": "', por ejemplo 'anthropic/claude-opus-4-7', 'deepseek/deepseek-v4-pro'.", "cli.model_quality_warning": "ADVERTENCIA DE CALIDAD DEL MODELO", + "cli.model_quality_warning_body": " no es un modelo frontier recomendado para Strix.\nLos escaneos de seguridad funcionan mejor con:\n", + "cli.model_quality_warning_footer": "\nPuedes continuar, pero los modelos más débiles pueden omitir vulnerabilidades o producir hallazgos de menor calidad.", "cli.interactive_setup_unavailable": "CONFIGURACIÓN INTERACTIVA NO DISPONIBLE", "cli.scan_preparation_failed": "FALLO EN PREPARACIÓN DEL ESCANEO", "cli.test_initiated": "Prueba de penetración iniciada", diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 9889f798..ff8df30d 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -203,6 +203,55 @@ class TestLocaleKeyConsistency: assert locale_file.exists(), f"Missing locale file: {locale_file}" +class TestCliHelpKeys: + """The CLI help and model-warning strings must translate in both locales.""" + + def test_workspace_file_help_translates(self): + set_language("en") + en_value = t("cli.workspace_file_help") + assert "sandbox workspace" in en_value + set_language("es") + es_value = t("cli.workspace_file_help") + assert "workspace del sandbox" in es_value + assert es_value != "cli.workspace_file_help" + + def test_examples_header_translates(self): + set_language("es") + assert t("cli.examples_header") == "Ejemplos:" + set_language("en") + assert t("cli.examples_header") == "Examples:" + + def test_model_quality_warning_body_translates(self): + set_language("en") + assert "frontier model" in t("cli.model_quality_warning_body") + set_language("es") + es_value = t("cli.model_quality_warning_body") + assert "modelo frontier" in es_value + assert es_value != "cli.model_quality_warning_body" + + def test_model_quality_warning_footer_translates(self): + set_language("en") + assert "weaker models" in t("cli.model_quality_warning_footer") + set_language("es") + es_value = t("cli.model_quality_warning_footer") + assert "modelos más débiles" in es_value + + def test_unknown_model_body_translates(self): + set_language("en") + assert "known OpenAI model" in t("cli.unknown_model_body") + set_language("es") + es_value = t("cli.unknown_model_body") + assert "modelo de OpenAI conocido" in es_value + + def test_unknown_model_hint_translates(self): + set_language("en") + assert "form, e.g." in t("cli.unknown_model_hint") + set_language("es") + es_value = t("cli.unknown_model_hint") + assert "por ejemplo" in es_value + + + class TestSettingsLanguageField: def test_settings_has_language_field(self): s = Settings()