From bd7bbb99c403ec0ea22ca10653e9969d4a95826d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 20 May 2026 15:26:10 +0800 Subject: [PATCH] feat: add Third-party category to /auth and fix QwenPaw banner display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactor PARTNER_SOURCES from dict list to frozen PartnerSource dataclass with is_available() and get_provider_display() methods - Add "Third-party" (第三方) category to /auth that only appears when at least one third-party source is detected on the machine - Fix banner to show real provider name (e.g. QwenPaw / Alibaba Cloud Bailian / qwen3.6-plus) instead of just QwenPaw / model - Add proper i18n translations for "Third-party" across all locales Co-Authored-By: Claude Opus 4.6 --- src/iac_code/__init__.py | 2 +- src/iac_code/commands/auth.py | 85 +++++++--- src/iac_code/commands/model.py | 4 +- src/iac_code/config.py | 41 ++++- .../i18n/locales/de/LC_MESSAGES/messages.po | 149 +++++++++--------- .../i18n/locales/es/LC_MESSAGES/messages.po | 149 +++++++++--------- .../i18n/locales/fr/LC_MESSAGES/messages.po | 149 +++++++++--------- .../i18n/locales/ja/LC_MESSAGES/messages.po | 149 +++++++++--------- .../i18n/locales/pt/LC_MESSAGES/messages.po | 149 +++++++++--------- .../i18n/locales/zh/LC_MESSAGES/messages.po | 149 +++++++++--------- src/iac_code/ui/banner.py | 7 +- tests/commands/test_auth_flows.py | 62 +++++--- tests/commands/test_auth_third_party.py | 137 ++++++++++++++++ tests/test_config.py | 66 +++++++- tests/ui/test_banner.py | 36 +++++ 15 files changed, 856 insertions(+), 478 deletions(-) create mode 100644 tests/commands/test_auth_third_party.py diff --git a/src/iac_code/__init__.py b/src/iac_code/__init__.py index 22cf2787..a37f6ecf 100644 --- a/src/iac_code/__init__.py +++ b/src/iac_code/__init__.py @@ -1,2 +1,2 @@ -__version__ = "0.2.0" +__version__ = "0.2.1" __release_date__ = "" diff --git a/src/iac_code/commands/auth.py b/src/iac_code/commands/auth.py index 35c6c5c8..8b541b57 100644 --- a/src/iac_code/commands/auth.py +++ b/src/iac_code/commands/auth.py @@ -14,10 +14,14 @@ from iac_code.config import ( _LEGACY_KEY_NAME_ALIASES, + PARTNER_SOURCES, + PartnerSource, _load_yaml, _save_yaml, get_active_provider_key, + get_available_partner_sources, get_credentials_path, + get_llm_source, get_provider_config, get_settings_path, ) @@ -595,6 +599,47 @@ def _get_active_key_name() -> str: return get_active_provider_key() or "" +def _third_party_auth_flow( + available_partners: list[PartnerSource], + current_llm_source: str, +) -> str | None | _BackSentinel: + """Second-level selection within the Third-party category.""" + candidates = list(available_partners) + if any(ps.key == current_llm_source for ps in PARTNER_SOURCES): + if not any(ps.key == current_llm_source for ps in candidates): + for ps in PARTNER_SOURCES: + if ps.key == current_llm_source: + candidates.append(ps) + break + + if len(candidates) == 0: + return _BACK + + options: list[str] = [] + default_idx = 0 + for i, ps in enumerate(candidates): + label = ps.display_name + if current_llm_source == ps.key: + label += _(" (current)") + default_idx = i + options.append(label) + + idx = _select(_("Select provider — {group}").format(group=_("Third-party")), options, default_index=default_idx) + if idx is None: + return _BACK + partner = candidates[idx] + + settings_path = get_settings_path() + config = _load_yaml(settings_path) + config.pop("activeProvider", None) + config["llm_source"] = partner.key + _save_yaml(settings_path, config) + return _("{status}: {provider}").format( + status=_("Configured"), + provider=partner.display_name, + ) + + def _llm_auth_flow(console, store) -> str | None | _BackSentinel: """LLM provider auth flow with two-step vendor group selection.""" active_key_name = _get_active_key_name() @@ -621,49 +666,42 @@ def _llm_auth_flow(console, store) -> str | None | _BackSentinel: provider_map: dict[str, LLMProvider] = {str(p["key_name"]): p for p in PROVIDERS} - from iac_code.config import PARTNER_SOURCES, get_llm_source - - num_partner_sources = len(PARTNER_SOURCES) - current_llm_source = get_llm_source() + available_partners = get_available_partner_sources() + is_current_partner = any(ps.key == current_llm_source for ps in PARTNER_SOURCES) + show_third_party = len(available_partners) > 0 or is_current_partner while True: - # Step 1: Select vendor group (partner sources shown at the top) group_options: list[str] = [] group_default_idx = 0 - for i, ps in enumerate(PARTNER_SOURCES): - label = ps["display_name"] - if current_llm_source == ps["key"]: + if show_third_party: + label = _("Third-party") + if is_current_partner: label += _(" (current)") - group_default_idx = i + group_default_idx = 0 group_options.append(label) for i, (group_name, keys) in enumerate(provider_groups): label = _(group_name) + offset = 1 if show_third_party else 0 if active_key_name in keys: label += _(" (current)") - group_default_idx = i + num_partner_sources + group_default_idx = i + offset group_options.append(label) group_idx = _select(_("Select provider"), group_options, default_index=group_default_idx) if group_idx is None: return _BACK - # Handle partner source selection - if group_idx < num_partner_sources: - partner = PARTNER_SOURCES[group_idx] - settings_path = get_settings_path() - config = _load_yaml(settings_path) - config.pop("activeProvider", None) - config["llm_source"] = partner["key"] - _save_yaml(settings_path, config) - return _("{status}: {provider}").format( - status=_("Configured"), - provider=partner["display_name"], - ) + if show_third_party and group_idx == 0: + result = _third_party_auth_flow(available_partners, current_llm_source) + if isinstance(result, _BackSentinel): + continue + return result - group_name, group_keys = provider_groups[group_idx - num_partner_sources] + offset = 1 if show_third_party else 0 + group_name, group_keys = provider_groups[group_idx - offset] group_providers = [provider_map[k] for k in group_keys if k in provider_map] # Step 2: Select provider within group (skip if only one) @@ -755,6 +793,7 @@ def _llm_auth_flow(console, store) -> str | None | _BackSentinel: _GROUP_NAME_MARKERS = [ + _("Third-party"), _("Alibaba Cloud"), _("ZhiPu AI"), _("Kimi"), diff --git a/src/iac_code/commands/model.py b/src/iac_code/commands/model.py index caa7f7c4..58e1264c 100644 --- a/src/iac_code/commands/model.py +++ b/src/iac_code/commands/model.py @@ -50,8 +50,8 @@ async def model_command(context: "CommandContext | None" = None, args: list[str] display_name = llm_source for ps in PARTNER_SOURCES: - if ps["key"] == llm_source: - display_name = ps["display_name"] + if ps.key == llm_source: + display_name = ps.display_name break return _( "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." diff --git a/src/iac_code/config.py b/src/iac_code/config.py index d3424337..a8112423 100644 --- a/src/iac_code/config.py +++ b/src/iac_code/config.py @@ -9,6 +9,7 @@ from __future__ import annotations import os +from dataclasses import dataclass from pathlib import Path from typing import Any @@ -179,11 +180,47 @@ def get_llm_source() -> str: return "local" -PARTNER_SOURCES: list[dict[str, str]] = [ - {"key": "qwenpaw", "display_name": "QwenPaw"}, +@dataclass(frozen=True) +class PartnerSource: + key: str + display_name: str + + def is_available(self) -> bool: + if self.key == "qwenpaw": + from iac_code.services.qwenpaw_source import _resolve_secret_dir + + return _resolve_secret_dir() is not None + return False + + def get_provider_display(self) -> str: + if self.key == "qwenpaw": + from iac_code.services.qwenpaw_source import load_from_qwenpaw + + try: + config = load_from_qwenpaw() + except Exception: + return "" + if config: + from iac_code.providers.registry import PROVIDER_REGISTRY + + desc = PROVIDER_REGISTRY.get(config.provider_key) + if desc: + from iac_code.i18n import _ + + return _(desc.display_name) + return config.provider_key + return "" + + +PARTNER_SOURCES: list[PartnerSource] = [ + PartnerSource(key="qwenpaw", display_name="QwenPaw"), ] +def get_available_partner_sources() -> list[PartnerSource]: + return [ps for ps in PARTNER_SOURCES if ps.is_available()] + + # --------------------------------------------------------------------------- # Path helpers # --------------------------------------------------------------------------- diff --git a/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po b/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po index e2051d98..f1354a05 100644 --- a/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: iac-code 0.2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-20 12:34+0800\n" +"POT-Creation-Date: 2026-05-20 15:23+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: de\n" @@ -543,217 +543,224 @@ msgstr "Eine frühere Sitzung fortsetzen" msgid "[conversation id or search term]" msgstr "[Konversations-ID oder Suchbegriff]" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:844 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:883 #: src/iac_code/ui/core/prompt_input.py:505 msgid "Navigate" msgstr "Navigieren" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:367 -#: src/iac_code/commands/auth.py:401 src/iac_code/commands/auth.py:408 -#: src/iac_code/commands/auth.py:432 src/iac_code/commands/auth.py:844 -#: src/iac_code/commands/auth.py:1034 src/iac_code/ui/core/prompt_input.py:505 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:371 +#: src/iac_code/commands/auth.py:405 src/iac_code/commands/auth.py:412 +#: src/iac_code/commands/auth.py:436 src/iac_code/commands/auth.py:883 +#: src/iac_code/commands/auth.py:1073 src/iac_code/ui/core/prompt_input.py:505 msgid "Confirm" msgstr "Bestätigen" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:365 -#: src/iac_code/commands/auth.py:367 src/iac_code/commands/auth.py:401 -#: src/iac_code/commands/auth.py:408 src/iac_code/commands/auth.py:432 -#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:929 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:369 +#: src/iac_code/commands/auth.py:371 src/iac_code/commands/auth.py:405 +#: src/iac_code/commands/auth.py:412 src/iac_code/commands/auth.py:436 +#: src/iac_code/commands/auth.py:883 src/iac_code/commands/auth.py:968 +#: src/iac_code/commands/auth.py:1073 msgid "Back" msgstr "Zurück" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Keep" msgstr "Behalten" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Re-enter" msgstr "Erneut eingeben" -#: src/iac_code/commands/auth.py:507 src/iac_code/commands/auth.py:638 -#: src/iac_code/commands/auth.py:645 src/iac_code/commands/auth.py:678 +#: src/iac_code/commands/auth.py:511 src/iac_code/commands/auth.py:623 +#: src/iac_code/commands/auth.py:681 src/iac_code/commands/auth.py:689 +#: src/iac_code/commands/auth.py:716 msgid " (current)" msgstr " (aktuell)" -#: src/iac_code/commands/auth.py:510 +#: src/iac_code/commands/auth.py:514 msgid "Custom model..." msgstr "Benutzerdefiniertes Modell …" -#: src/iac_code/commands/auth.py:513 +#: src/iac_code/commands/auth.py:517 #, python-brace-format msgid "Select model for {provider}" msgstr "Modell für {provider} auswählen" -#: src/iac_code/commands/auth.py:515 +#: src/iac_code/commands/auth.py:519 msgid "Select model" msgstr "Modell auswählen" -#: src/iac_code/commands/auth.py:523 +#: src/iac_code/commands/auth.py:527 msgid "Enter custom model name: " msgstr "Benutzerdefinierten Modellnamen eingeben: " -#: src/iac_code/commands/auth.py:549 +#: src/iac_code/commands/auth.py:553 msgid "Error: console not available" msgstr "Fehler: Konsole nicht verfügbar" -#: src/iac_code/commands/auth.py:576 +#: src/iac_code/commands/auth.py:580 msgid "Configure LLM Provider" msgstr "LLM-Anbieter konfigurieren" -#: src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:581 msgid "Configure IaC Cloud Service" msgstr "IaC-Cloud-Dienst konfigurieren" -#: src/iac_code/commands/auth.py:579 +#: src/iac_code/commands/auth.py:583 msgid "Select configuration type" msgstr "Konfigurationstyp auswählen" -#: src/iac_code/commands/auth.py:581 src/iac_code/commands/auth.py:703 -#: src/iac_code/commands/auth.py:719 src/iac_code/commands/auth.py:797 -#: src/iac_code/commands/auth.py:970 src/iac_code/commands/auth.py:1011 +#: src/iac_code/commands/auth.py:585 src/iac_code/commands/auth.py:741 +#: src/iac_code/commands/auth.py:757 src/iac_code/commands/auth.py:836 +#: src/iac_code/commands/auth.py:1009 src/iac_code/commands/auth.py:1050 msgid "Auth cancelled" msgstr "Authentifizierung abgebrochen" -#: src/iac_code/commands/auth.py:649 -msgid "Select provider" -msgstr "Anbieter auswählen" +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:721 +#: src/iac_code/commands/auth.py:811 +#, python-brace-format +msgid "Select provider — {group}" +msgstr "Anbieter auswählen — {group}" -#: src/iac_code/commands/auth.py:661 +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:679 +#: src/iac_code/commands/auth.py:796 +msgid "Third-party" +msgstr "Drittanbieter" + +#: src/iac_code/commands/auth.py:637 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:662 src/iac_code/commands/auth.py:751 +#: src/iac_code/commands/auth.py:638 src/iac_code/commands/auth.py:789 msgid "Configured" msgstr "Konfiguriert" -#: src/iac_code/commands/auth.py:683 src/iac_code/commands/auth.py:772 -#, python-brace-format -msgid "Select provider — {group}" -msgstr "Anbieter auswählen — {group}" +#: src/iac_code/commands/auth.py:693 +msgid "Select provider" +msgstr "Anbieter auswählen" -#: src/iac_code/commands/auth.py:696 +#: src/iac_code/commands/auth.py:734 #, python-brace-format msgid "Configure {provider}" msgstr "{provider} konfigurieren" -#: src/iac_code/commands/auth.py:712 +#: src/iac_code/commands/auth.py:750 #, python-brace-format msgid "Enter API key for {provider}" msgstr "API-Key für {provider} eingeben" -#: src/iac_code/commands/auth.py:750 +#: src/iac_code/commands/auth.py:788 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:758 src/iac_code/commands/auth.py:779 +#: src/iac_code/commands/auth.py:797 src/iac_code/commands/auth.py:818 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:759 src/iac_code/providers/registry.py:423 +#: src/iac_code/commands/auth.py:798 src/iac_code/providers/registry.py:423 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:760 +#: src/iac_code/commands/auth.py:799 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:761 +#: src/iac_code/commands/auth.py:800 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:762 src/iac_code/providers/registry.py:425 +#: src/iac_code/commands/auth.py:801 src/iac_code/providers/registry.py:425 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:763 +#: src/iac_code/commands/auth.py:802 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:764 src/iac_code/providers/registry.py:416 +#: src/iac_code/commands/auth.py:803 src/iac_code/providers/registry.py:416 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:765 src/iac_code/providers/registry.py:414 +#: src/iac_code/commands/auth.py:804 src/iac_code/providers/registry.py:414 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:766 src/iac_code/providers/registry.py:415 +#: src/iac_code/commands/auth.py:805 src/iac_code/providers/registry.py:415 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:767 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:806 src/iac_code/providers/registry.py:418 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:768 src/iac_code/providers/registry.py:431 +#: src/iac_code/commands/auth.py:807 src/iac_code/providers/registry.py:431 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:769 src/iac_code/providers/registry.py:430 +#: src/iac_code/commands/auth.py:808 src/iac_code/providers/registry.py:430 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:770 +#: src/iac_code/commands/auth.py:809 msgid "Local" msgstr "Lokal" -#: src/iac_code/commands/auth.py:771 +#: src/iac_code/commands/auth.py:810 msgid "Compatible" msgstr "Kompatibel" -#: src/iac_code/commands/auth.py:788 +#: src/iac_code/commands/auth.py:827 msgid "Select Cloud Provider" msgstr "Cloud-Anbieter auswählen" -#: src/iac_code/commands/auth.py:804 +#: src/iac_code/commands/auth.py:843 msgid "Credential" msgstr "Anmeldedaten" -#: src/iac_code/commands/auth.py:805 src/iac_code/commands/auth.py:902 -#: src/iac_code/commands/auth.py:1007 src/iac_code/ui/renderer.py:418 +#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:941 +#: src/iac_code/commands/auth.py:1046 src/iac_code/ui/renderer.py:418 msgid "Region" msgstr "Region" -#: src/iac_code/commands/auth.py:807 +#: src/iac_code/commands/auth.py:846 msgid "Configure Alibaba Cloud" msgstr "Alibaba Cloud konfigurieren" -#: src/iac_code/commands/auth.py:890 +#: src/iac_code/commands/auth.py:929 msgid "Current configuration" msgstr "Aktuelle Konfiguration" -#: src/iac_code/commands/auth.py:892 +#: src/iac_code/commands/auth.py:931 msgid "Mode" msgstr "Modus" -#: src/iac_code/commands/auth.py:899 +#: src/iac_code/commands/auth.py:938 msgid "(not set)" msgstr "(nicht gesetzt)" -#: src/iac_code/commands/auth.py:916 +#: src/iac_code/commands/auth.py:955 msgid "Configure Alibaba Cloud credentials" msgstr "Alibaba Cloud-Anmeldedaten konfigurieren" -#: src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:968 msgid "Reconfigure credential" msgstr "Anmeldedaten neu konfigurieren" -#: src/iac_code/commands/auth.py:942 +#: src/iac_code/commands/auth.py:981 msgid "Select credential type" msgstr "Anmeldedatentyp auswählen" -#: src/iac_code/commands/auth.py:992 +#: src/iac_code/commands/auth.py:1031 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "Konfiguriert: Alibaba Cloud-Anmeldedaten unter ~/.iac-code gespeichert" -#: src/iac_code/commands/auth.py:999 +#: src/iac_code/commands/auth.py:1038 msgid "Configure Alibaba Cloud region" msgstr "Alibaba Cloud-Region konfigurieren" -#: src/iac_code/commands/auth.py:1025 +#: src/iac_code/commands/auth.py:1064 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Konfiguriert: Alibaba Cloud-Region unter ~/.iac-code gespeichert" @@ -1585,23 +1592,23 @@ msgstr "ROS Stack" msgid "CloudStackInstances" msgstr "CloudStackInstances" -#: src/iac_code/ui/banner.py:68 +#: src/iac_code/ui/banner.py:71 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "Ihr KI-gestützter Infrastructure-as-Code-Assistent" -#: src/iac_code/ui/banner.py:92 +#: src/iac_code/ui/banner.py:95 msgid "Welcome back" msgstr "Willkommen zurück" -#: src/iac_code/ui/banner.py:98 +#: src/iac_code/ui/banner.py:101 msgid "Session" msgstr "Sitzung" -#: src/iac_code/ui/banner.py:108 +#: src/iac_code/ui/banner.py:111 msgid "Debug mode" msgstr "Debug-Modus" -#: src/iac_code/ui/banner.py:109 +#: src/iac_code/ui/banner.py:112 msgid "Log file" msgstr "Protokolldatei" diff --git a/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po b/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po index 8f18a8e1..306cad3b 100644 --- a/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: iac-code 0.2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-20 12:34+0800\n" +"POT-Creation-Date: 2026-05-20 15:23+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: es\n" @@ -544,217 +544,224 @@ msgstr "Reanudar una sesión anterior" msgid "[conversation id or search term]" msgstr "[id de conversación o término de búsqueda]" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:844 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:883 #: src/iac_code/ui/core/prompt_input.py:505 msgid "Navigate" msgstr "Navegar" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:367 -#: src/iac_code/commands/auth.py:401 src/iac_code/commands/auth.py:408 -#: src/iac_code/commands/auth.py:432 src/iac_code/commands/auth.py:844 -#: src/iac_code/commands/auth.py:1034 src/iac_code/ui/core/prompt_input.py:505 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:371 +#: src/iac_code/commands/auth.py:405 src/iac_code/commands/auth.py:412 +#: src/iac_code/commands/auth.py:436 src/iac_code/commands/auth.py:883 +#: src/iac_code/commands/auth.py:1073 src/iac_code/ui/core/prompt_input.py:505 msgid "Confirm" msgstr "Confirmar" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:365 -#: src/iac_code/commands/auth.py:367 src/iac_code/commands/auth.py:401 -#: src/iac_code/commands/auth.py:408 src/iac_code/commands/auth.py:432 -#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:929 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:369 +#: src/iac_code/commands/auth.py:371 src/iac_code/commands/auth.py:405 +#: src/iac_code/commands/auth.py:412 src/iac_code/commands/auth.py:436 +#: src/iac_code/commands/auth.py:883 src/iac_code/commands/auth.py:968 +#: src/iac_code/commands/auth.py:1073 msgid "Back" msgstr "Atrás" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Keep" msgstr "Conservar" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Re-enter" msgstr "Volver a introducir" -#: src/iac_code/commands/auth.py:507 src/iac_code/commands/auth.py:638 -#: src/iac_code/commands/auth.py:645 src/iac_code/commands/auth.py:678 +#: src/iac_code/commands/auth.py:511 src/iac_code/commands/auth.py:623 +#: src/iac_code/commands/auth.py:681 src/iac_code/commands/auth.py:689 +#: src/iac_code/commands/auth.py:716 msgid " (current)" msgstr " (actual)" -#: src/iac_code/commands/auth.py:510 +#: src/iac_code/commands/auth.py:514 msgid "Custom model..." msgstr "Modelo personalizado..." -#: src/iac_code/commands/auth.py:513 +#: src/iac_code/commands/auth.py:517 #, python-brace-format msgid "Select model for {provider}" msgstr "Seleccionar modelo para {provider}" -#: src/iac_code/commands/auth.py:515 +#: src/iac_code/commands/auth.py:519 msgid "Select model" msgstr "Seleccionar modelo" -#: src/iac_code/commands/auth.py:523 +#: src/iac_code/commands/auth.py:527 msgid "Enter custom model name: " msgstr "Introduzca el nombre del modelo personalizado: " -#: src/iac_code/commands/auth.py:549 +#: src/iac_code/commands/auth.py:553 msgid "Error: console not available" msgstr "Error: la consola no está disponible" -#: src/iac_code/commands/auth.py:576 +#: src/iac_code/commands/auth.py:580 msgid "Configure LLM Provider" msgstr "Configurar proveedor LLM" -#: src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:581 msgid "Configure IaC Cloud Service" msgstr "Configurar servicio cloud IaC" -#: src/iac_code/commands/auth.py:579 +#: src/iac_code/commands/auth.py:583 msgid "Select configuration type" msgstr "Seleccionar tipo de configuración" -#: src/iac_code/commands/auth.py:581 src/iac_code/commands/auth.py:703 -#: src/iac_code/commands/auth.py:719 src/iac_code/commands/auth.py:797 -#: src/iac_code/commands/auth.py:970 src/iac_code/commands/auth.py:1011 +#: src/iac_code/commands/auth.py:585 src/iac_code/commands/auth.py:741 +#: src/iac_code/commands/auth.py:757 src/iac_code/commands/auth.py:836 +#: src/iac_code/commands/auth.py:1009 src/iac_code/commands/auth.py:1050 msgid "Auth cancelled" msgstr "Autenticación cancelada" -#: src/iac_code/commands/auth.py:649 -msgid "Select provider" -msgstr "Seleccionar proveedor" +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:721 +#: src/iac_code/commands/auth.py:811 +#, python-brace-format +msgid "Select provider — {group}" +msgstr "Seleccionar proveedor — {group}" -#: src/iac_code/commands/auth.py:661 +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:679 +#: src/iac_code/commands/auth.py:796 +msgid "Third-party" +msgstr "Terceros" + +#: src/iac_code/commands/auth.py:637 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:662 src/iac_code/commands/auth.py:751 +#: src/iac_code/commands/auth.py:638 src/iac_code/commands/auth.py:789 msgid "Configured" msgstr "Configurado" -#: src/iac_code/commands/auth.py:683 src/iac_code/commands/auth.py:772 -#, python-brace-format -msgid "Select provider — {group}" -msgstr "Seleccionar proveedor — {group}" +#: src/iac_code/commands/auth.py:693 +msgid "Select provider" +msgstr "Seleccionar proveedor" -#: src/iac_code/commands/auth.py:696 +#: src/iac_code/commands/auth.py:734 #, python-brace-format msgid "Configure {provider}" msgstr "Configurar {provider}" -#: src/iac_code/commands/auth.py:712 +#: src/iac_code/commands/auth.py:750 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Introduzca la API key para {provider}" -#: src/iac_code/commands/auth.py:750 +#: src/iac_code/commands/auth.py:788 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:758 src/iac_code/commands/auth.py:779 +#: src/iac_code/commands/auth.py:797 src/iac_code/commands/auth.py:818 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:759 src/iac_code/providers/registry.py:423 +#: src/iac_code/commands/auth.py:798 src/iac_code/providers/registry.py:423 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:760 +#: src/iac_code/commands/auth.py:799 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:761 +#: src/iac_code/commands/auth.py:800 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:762 src/iac_code/providers/registry.py:425 +#: src/iac_code/commands/auth.py:801 src/iac_code/providers/registry.py:425 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:763 +#: src/iac_code/commands/auth.py:802 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:764 src/iac_code/providers/registry.py:416 +#: src/iac_code/commands/auth.py:803 src/iac_code/providers/registry.py:416 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:765 src/iac_code/providers/registry.py:414 +#: src/iac_code/commands/auth.py:804 src/iac_code/providers/registry.py:414 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:766 src/iac_code/providers/registry.py:415 +#: src/iac_code/commands/auth.py:805 src/iac_code/providers/registry.py:415 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:767 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:806 src/iac_code/providers/registry.py:418 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:768 src/iac_code/providers/registry.py:431 +#: src/iac_code/commands/auth.py:807 src/iac_code/providers/registry.py:431 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:769 src/iac_code/providers/registry.py:430 +#: src/iac_code/commands/auth.py:808 src/iac_code/providers/registry.py:430 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:770 +#: src/iac_code/commands/auth.py:809 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:771 +#: src/iac_code/commands/auth.py:810 msgid "Compatible" msgstr "Compatible" -#: src/iac_code/commands/auth.py:788 +#: src/iac_code/commands/auth.py:827 msgid "Select Cloud Provider" msgstr "Seleccionar proveedor de cloud" -#: src/iac_code/commands/auth.py:804 +#: src/iac_code/commands/auth.py:843 msgid "Credential" msgstr "Credencial" -#: src/iac_code/commands/auth.py:805 src/iac_code/commands/auth.py:902 -#: src/iac_code/commands/auth.py:1007 src/iac_code/ui/renderer.py:418 +#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:941 +#: src/iac_code/commands/auth.py:1046 src/iac_code/ui/renderer.py:418 msgid "Region" msgstr "Región" -#: src/iac_code/commands/auth.py:807 +#: src/iac_code/commands/auth.py:846 msgid "Configure Alibaba Cloud" msgstr "Configurar Alibaba Cloud" -#: src/iac_code/commands/auth.py:890 +#: src/iac_code/commands/auth.py:929 msgid "Current configuration" msgstr "Configuración actual" -#: src/iac_code/commands/auth.py:892 +#: src/iac_code/commands/auth.py:931 msgid "Mode" msgstr "Modo" -#: src/iac_code/commands/auth.py:899 +#: src/iac_code/commands/auth.py:938 msgid "(not set)" msgstr "(sin definir)" -#: src/iac_code/commands/auth.py:916 +#: src/iac_code/commands/auth.py:955 msgid "Configure Alibaba Cloud credentials" msgstr "Configurar credenciales de Alibaba Cloud" -#: src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:968 msgid "Reconfigure credential" msgstr "Reconfigurar la credencial" -#: src/iac_code/commands/auth.py:942 +#: src/iac_code/commands/auth.py:981 msgid "Select credential type" msgstr "Seleccionar tipo de credencial" -#: src/iac_code/commands/auth.py:992 +#: src/iac_code/commands/auth.py:1031 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "Configurado: credenciales de Alibaba Cloud guardadas en ~/.iac-code" -#: src/iac_code/commands/auth.py:999 +#: src/iac_code/commands/auth.py:1038 msgid "Configure Alibaba Cloud region" msgstr "Configurar la región de Alibaba Cloud" -#: src/iac_code/commands/auth.py:1025 +#: src/iac_code/commands/auth.py:1064 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Configurado: región de Alibaba Cloud guardada en ~/.iac-code" @@ -1585,23 +1592,23 @@ msgstr "ROS Stack" msgid "CloudStackInstances" msgstr "CloudStackInstances" -#: src/iac_code/ui/banner.py:68 +#: src/iac_code/ui/banner.py:71 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "Su asistente de infraestructura como código con IA" -#: src/iac_code/ui/banner.py:92 +#: src/iac_code/ui/banner.py:95 msgid "Welcome back" msgstr "Bienvenido de nuevo" -#: src/iac_code/ui/banner.py:98 +#: src/iac_code/ui/banner.py:101 msgid "Session" msgstr "Sesión" -#: src/iac_code/ui/banner.py:108 +#: src/iac_code/ui/banner.py:111 msgid "Debug mode" msgstr "Modo depuración" -#: src/iac_code/ui/banner.py:109 +#: src/iac_code/ui/banner.py:112 msgid "Log file" msgstr "Archivo de registro" diff --git a/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po b/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po index 33f1a912..32e2441a 100644 --- a/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: iac-code 0.2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-20 12:34+0800\n" +"POT-Creation-Date: 2026-05-20 15:23+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: fr\n" @@ -540,220 +540,227 @@ msgstr "Reprendre une session précédente" msgid "[conversation id or search term]" msgstr "[identifiant de conversation ou terme de recherche]" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:844 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:883 #: src/iac_code/ui/core/prompt_input.py:505 msgid "Navigate" msgstr "Naviguer" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:367 -#: src/iac_code/commands/auth.py:401 src/iac_code/commands/auth.py:408 -#: src/iac_code/commands/auth.py:432 src/iac_code/commands/auth.py:844 -#: src/iac_code/commands/auth.py:1034 src/iac_code/ui/core/prompt_input.py:505 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:371 +#: src/iac_code/commands/auth.py:405 src/iac_code/commands/auth.py:412 +#: src/iac_code/commands/auth.py:436 src/iac_code/commands/auth.py:883 +#: src/iac_code/commands/auth.py:1073 src/iac_code/ui/core/prompt_input.py:505 msgid "Confirm" msgstr "Confirmer" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:365 -#: src/iac_code/commands/auth.py:367 src/iac_code/commands/auth.py:401 -#: src/iac_code/commands/auth.py:408 src/iac_code/commands/auth.py:432 -#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:929 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:369 +#: src/iac_code/commands/auth.py:371 src/iac_code/commands/auth.py:405 +#: src/iac_code/commands/auth.py:412 src/iac_code/commands/auth.py:436 +#: src/iac_code/commands/auth.py:883 src/iac_code/commands/auth.py:968 +#: src/iac_code/commands/auth.py:1073 msgid "Back" msgstr "Retour" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Keep" msgstr "Conserver" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Re-enter" msgstr "Saisir à nouveau" -#: src/iac_code/commands/auth.py:507 src/iac_code/commands/auth.py:638 -#: src/iac_code/commands/auth.py:645 src/iac_code/commands/auth.py:678 +#: src/iac_code/commands/auth.py:511 src/iac_code/commands/auth.py:623 +#: src/iac_code/commands/auth.py:681 src/iac_code/commands/auth.py:689 +#: src/iac_code/commands/auth.py:716 msgid " (current)" msgstr " (actuel)" -#: src/iac_code/commands/auth.py:510 +#: src/iac_code/commands/auth.py:514 msgid "Custom model..." msgstr "Modèle personnalisé…" -#: src/iac_code/commands/auth.py:513 +#: src/iac_code/commands/auth.py:517 #, python-brace-format msgid "Select model for {provider}" msgstr "Sélectionner le modèle pour {provider}" -#: src/iac_code/commands/auth.py:515 +#: src/iac_code/commands/auth.py:519 msgid "Select model" msgstr "Sélectionner le modèle" -#: src/iac_code/commands/auth.py:523 +#: src/iac_code/commands/auth.py:527 msgid "Enter custom model name: " msgstr "Saisir le nom du modèle personnalisé : " -#: src/iac_code/commands/auth.py:549 +#: src/iac_code/commands/auth.py:553 msgid "Error: console not available" msgstr "Erreur : console indisponible" -#: src/iac_code/commands/auth.py:576 +#: src/iac_code/commands/auth.py:580 msgid "Configure LLM Provider" msgstr "Configurer le fournisseur LLM" -#: src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:581 msgid "Configure IaC Cloud Service" msgstr "Configurer le service cloud IaC" -#: src/iac_code/commands/auth.py:579 +#: src/iac_code/commands/auth.py:583 msgid "Select configuration type" msgstr "Sélectionner le type de configuration" -#: src/iac_code/commands/auth.py:581 src/iac_code/commands/auth.py:703 -#: src/iac_code/commands/auth.py:719 src/iac_code/commands/auth.py:797 -#: src/iac_code/commands/auth.py:970 src/iac_code/commands/auth.py:1011 +#: src/iac_code/commands/auth.py:585 src/iac_code/commands/auth.py:741 +#: src/iac_code/commands/auth.py:757 src/iac_code/commands/auth.py:836 +#: src/iac_code/commands/auth.py:1009 src/iac_code/commands/auth.py:1050 msgid "Auth cancelled" msgstr "Authentification annulée" -#: src/iac_code/commands/auth.py:649 -msgid "Select provider" -msgstr "Sélectionner le fournisseur" +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:721 +#: src/iac_code/commands/auth.py:811 +#, python-brace-format +msgid "Select provider — {group}" +msgstr "Sélectionner le fournisseur — {group}" -#: src/iac_code/commands/auth.py:661 +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:679 +#: src/iac_code/commands/auth.py:796 +msgid "Third-party" +msgstr "Tiers" + +#: src/iac_code/commands/auth.py:637 #, python-brace-format msgid "{status}: {provider}" msgstr "{status} : {provider}" -#: src/iac_code/commands/auth.py:662 src/iac_code/commands/auth.py:751 +#: src/iac_code/commands/auth.py:638 src/iac_code/commands/auth.py:789 msgid "Configured" msgstr "Configuré" -#: src/iac_code/commands/auth.py:683 src/iac_code/commands/auth.py:772 -#, python-brace-format -msgid "Select provider — {group}" -msgstr "Sélectionner le fournisseur — {group}" +#: src/iac_code/commands/auth.py:693 +msgid "Select provider" +msgstr "Sélectionner le fournisseur" -#: src/iac_code/commands/auth.py:696 +#: src/iac_code/commands/auth.py:734 #, python-brace-format msgid "Configure {provider}" msgstr "Configurer {provider}" -#: src/iac_code/commands/auth.py:712 +#: src/iac_code/commands/auth.py:750 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Saisir la clé API pour {provider}" -#: src/iac_code/commands/auth.py:750 +#: src/iac_code/commands/auth.py:788 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status} : {provider} / {model}" -#: src/iac_code/commands/auth.py:758 src/iac_code/commands/auth.py:779 +#: src/iac_code/commands/auth.py:797 src/iac_code/commands/auth.py:818 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:759 src/iac_code/providers/registry.py:423 +#: src/iac_code/commands/auth.py:798 src/iac_code/providers/registry.py:423 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:760 +#: src/iac_code/commands/auth.py:799 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:761 +#: src/iac_code/commands/auth.py:800 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:762 src/iac_code/providers/registry.py:425 +#: src/iac_code/commands/auth.py:801 src/iac_code/providers/registry.py:425 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:763 +#: src/iac_code/commands/auth.py:802 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:764 src/iac_code/providers/registry.py:416 +#: src/iac_code/commands/auth.py:803 src/iac_code/providers/registry.py:416 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:765 src/iac_code/providers/registry.py:414 +#: src/iac_code/commands/auth.py:804 src/iac_code/providers/registry.py:414 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:766 src/iac_code/providers/registry.py:415 +#: src/iac_code/commands/auth.py:805 src/iac_code/providers/registry.py:415 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:767 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:806 src/iac_code/providers/registry.py:418 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:768 src/iac_code/providers/registry.py:431 +#: src/iac_code/commands/auth.py:807 src/iac_code/providers/registry.py:431 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:769 src/iac_code/providers/registry.py:430 +#: src/iac_code/commands/auth.py:808 src/iac_code/providers/registry.py:430 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:770 +#: src/iac_code/commands/auth.py:809 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:771 +#: src/iac_code/commands/auth.py:810 msgid "Compatible" msgstr "Compatible" -#: src/iac_code/commands/auth.py:788 +#: src/iac_code/commands/auth.py:827 msgid "Select Cloud Provider" msgstr "Sélectionner le fournisseur cloud" -#: src/iac_code/commands/auth.py:804 +#: src/iac_code/commands/auth.py:843 msgid "Credential" msgstr "Identifiants" -#: src/iac_code/commands/auth.py:805 src/iac_code/commands/auth.py:902 -#: src/iac_code/commands/auth.py:1007 src/iac_code/ui/renderer.py:418 +#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:941 +#: src/iac_code/commands/auth.py:1046 src/iac_code/ui/renderer.py:418 msgid "Region" msgstr "Région" -#: src/iac_code/commands/auth.py:807 +#: src/iac_code/commands/auth.py:846 msgid "Configure Alibaba Cloud" msgstr "Configurer Alibaba Cloud" -#: src/iac_code/commands/auth.py:890 +#: src/iac_code/commands/auth.py:929 msgid "Current configuration" msgstr "Configuration actuelle" -#: src/iac_code/commands/auth.py:892 +#: src/iac_code/commands/auth.py:931 msgid "Mode" msgstr "Mode" -#: src/iac_code/commands/auth.py:899 +#: src/iac_code/commands/auth.py:938 msgid "(not set)" msgstr "(non défini)" -#: src/iac_code/commands/auth.py:916 +#: src/iac_code/commands/auth.py:955 msgid "Configure Alibaba Cloud credentials" msgstr "Configurer les identifiants Alibaba Cloud" -#: src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:968 msgid "Reconfigure credential" msgstr "Reconfigurer les identifiants" -#: src/iac_code/commands/auth.py:942 +#: src/iac_code/commands/auth.py:981 msgid "Select credential type" msgstr "Sélectionner le type d’identifiants" -#: src/iac_code/commands/auth.py:992 +#: src/iac_code/commands/auth.py:1031 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "" "Configured: Alibaba Cloud credentials saved to ~/.iac-codeConfigured: " "Alibaba Cloud credentials saved to ~/.iac-codeConfiguration effectuée : " "identifiants Alibaba Cloud enregistrés dans ~/.iac-code" -#: src/iac_code/commands/auth.py:999 +#: src/iac_code/commands/auth.py:1038 msgid "Configure Alibaba Cloud region" msgstr "Configurer la région Alibaba Cloud" -#: src/iac_code/commands/auth.py:1025 +#: src/iac_code/commands/auth.py:1064 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "" "Configured: Alibaba Cloud region saved to ~/.iac-codeConfigured: Alibaba " @@ -1585,23 +1592,23 @@ msgstr "ROS Stack" msgid "CloudStackInstances" msgstr "CloudStackInstances" -#: src/iac_code/ui/banner.py:68 +#: src/iac_code/ui/banner.py:71 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "Votre assistant Infrastructure as Code assisté par IA" -#: src/iac_code/ui/banner.py:92 +#: src/iac_code/ui/banner.py:95 msgid "Welcome back" msgstr "Bon retour" -#: src/iac_code/ui/banner.py:98 +#: src/iac_code/ui/banner.py:101 msgid "Session" msgstr "Session" -#: src/iac_code/ui/banner.py:108 +#: src/iac_code/ui/banner.py:111 msgid "Debug mode" msgstr "Mode debug" -#: src/iac_code/ui/banner.py:109 +#: src/iac_code/ui/banner.py:112 msgid "Log file" msgstr "Fichier journal" diff --git a/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po b/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po index d17fde4e..d0884a53 100644 --- a/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: iac-code 0.2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-20 12:34+0800\n" +"POT-Creation-Date: 2026-05-20 15:23+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: ja\n" @@ -523,219 +523,226 @@ msgstr "以前のセッションを再開します" msgid "[conversation id or search term]" msgstr "[会話 ID または検索語]" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:844 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:883 #: src/iac_code/ui/core/prompt_input.py:505 msgid "Navigate" msgstr "移動" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:367 -#: src/iac_code/commands/auth.py:401 src/iac_code/commands/auth.py:408 -#: src/iac_code/commands/auth.py:432 src/iac_code/commands/auth.py:844 -#: src/iac_code/commands/auth.py:1034 src/iac_code/ui/core/prompt_input.py:505 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:371 +#: src/iac_code/commands/auth.py:405 src/iac_code/commands/auth.py:412 +#: src/iac_code/commands/auth.py:436 src/iac_code/commands/auth.py:883 +#: src/iac_code/commands/auth.py:1073 src/iac_code/ui/core/prompt_input.py:505 msgid "Confirm" msgstr "確認" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:365 -#: src/iac_code/commands/auth.py:367 src/iac_code/commands/auth.py:401 -#: src/iac_code/commands/auth.py:408 src/iac_code/commands/auth.py:432 -#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:929 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:369 +#: src/iac_code/commands/auth.py:371 src/iac_code/commands/auth.py:405 +#: src/iac_code/commands/auth.py:412 src/iac_code/commands/auth.py:436 +#: src/iac_code/commands/auth.py:883 src/iac_code/commands/auth.py:968 +#: src/iac_code/commands/auth.py:1073 msgid "Back" msgstr "戻る" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Keep" msgstr "維持" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Re-enter" msgstr "再入力" -#: src/iac_code/commands/auth.py:507 src/iac_code/commands/auth.py:638 -#: src/iac_code/commands/auth.py:645 src/iac_code/commands/auth.py:678 +#: src/iac_code/commands/auth.py:511 src/iac_code/commands/auth.py:623 +#: src/iac_code/commands/auth.py:681 src/iac_code/commands/auth.py:689 +#: src/iac_code/commands/auth.py:716 msgid " (current)" msgstr " (現在)" -#: src/iac_code/commands/auth.py:510 +#: src/iac_code/commands/auth.py:514 msgid "Custom model..." msgstr "カスタムモデル…" -#: src/iac_code/commands/auth.py:513 +#: src/iac_code/commands/auth.py:517 #, python-brace-format msgid "Select model for {provider}" msgstr "{provider} のモデルを選択してください" -#: src/iac_code/commands/auth.py:515 +#: src/iac_code/commands/auth.py:519 msgid "Select model" msgstr "モデルを選択" -#: src/iac_code/commands/auth.py:523 +#: src/iac_code/commands/auth.py:527 msgid "Enter custom model name: " msgstr "カスタムモデル名を入力してください:" -#: src/iac_code/commands/auth.py:549 +#: src/iac_code/commands/auth.py:553 msgid "Error: console not available" msgstr "エラー:コンソールを使用できません" -#: src/iac_code/commands/auth.py:576 +#: src/iac_code/commands/auth.py:580 msgid "Configure LLM Provider" msgstr "LLM プロバイダーを設定" -#: src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:581 msgid "Configure IaC Cloud Service" msgstr "IaC クラウドサービスを設定" -#: src/iac_code/commands/auth.py:579 +#: src/iac_code/commands/auth.py:583 msgid "Select configuration type" msgstr "設定の種類を選択" -#: src/iac_code/commands/auth.py:581 src/iac_code/commands/auth.py:703 -#: src/iac_code/commands/auth.py:719 src/iac_code/commands/auth.py:797 -#: src/iac_code/commands/auth.py:970 src/iac_code/commands/auth.py:1011 +#: src/iac_code/commands/auth.py:585 src/iac_code/commands/auth.py:741 +#: src/iac_code/commands/auth.py:757 src/iac_code/commands/auth.py:836 +#: src/iac_code/commands/auth.py:1009 src/iac_code/commands/auth.py:1050 msgid "Auth cancelled" msgstr "認証をキャンセルしました" -#: src/iac_code/commands/auth.py:649 -msgid "Select provider" -msgstr "プロバイダーを選択" +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:721 +#: src/iac_code/commands/auth.py:811 +#, python-brace-format +msgid "Select provider — {group}" +msgstr "プロバイダーを選択 — {group}" -#: src/iac_code/commands/auth.py:661 +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:679 +#: src/iac_code/commands/auth.py:796 +msgid "Third-party" +msgstr "サードパーティ" + +#: src/iac_code/commands/auth.py:637 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}:{provider}" -#: src/iac_code/commands/auth.py:662 src/iac_code/commands/auth.py:751 +#: src/iac_code/commands/auth.py:638 src/iac_code/commands/auth.py:789 msgid "Configured" msgstr "設定済み" -#: src/iac_code/commands/auth.py:683 src/iac_code/commands/auth.py:772 -#, python-brace-format -msgid "Select provider — {group}" -msgstr "プロバイダーを選択 — {group}" +#: src/iac_code/commands/auth.py:693 +msgid "Select provider" +msgstr "プロバイダーを選択" -#: src/iac_code/commands/auth.py:696 +#: src/iac_code/commands/auth.py:734 #, python-brace-format msgid "Configure {provider}" msgstr "{provider} を設定" -#: src/iac_code/commands/auth.py:712 +#: src/iac_code/commands/auth.py:750 #, python-brace-format msgid "Enter API key for {provider}" msgstr "{provider} の API key を入力してください" -#: src/iac_code/commands/auth.py:750 +#: src/iac_code/commands/auth.py:788 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}:{provider} / {model}" -#: src/iac_code/commands/auth.py:758 src/iac_code/commands/auth.py:779 +#: src/iac_code/commands/auth.py:797 src/iac_code/commands/auth.py:818 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:759 src/iac_code/providers/registry.py:423 +#: src/iac_code/commands/auth.py:798 src/iac_code/providers/registry.py:423 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:760 +#: src/iac_code/commands/auth.py:799 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:761 +#: src/iac_code/commands/auth.py:800 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:762 src/iac_code/providers/registry.py:425 +#: src/iac_code/commands/auth.py:801 src/iac_code/providers/registry.py:425 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:763 +#: src/iac_code/commands/auth.py:802 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:764 src/iac_code/providers/registry.py:416 +#: src/iac_code/commands/auth.py:803 src/iac_code/providers/registry.py:416 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:765 src/iac_code/providers/registry.py:414 +#: src/iac_code/commands/auth.py:804 src/iac_code/providers/registry.py:414 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:766 src/iac_code/providers/registry.py:415 +#: src/iac_code/commands/auth.py:805 src/iac_code/providers/registry.py:415 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:767 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:806 src/iac_code/providers/registry.py:418 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:768 src/iac_code/providers/registry.py:431 +#: src/iac_code/commands/auth.py:807 src/iac_code/providers/registry.py:431 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:769 src/iac_code/providers/registry.py:430 +#: src/iac_code/commands/auth.py:808 src/iac_code/providers/registry.py:430 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:770 +#: src/iac_code/commands/auth.py:809 msgid "Local" msgstr "ローカル" -#: src/iac_code/commands/auth.py:771 +#: src/iac_code/commands/auth.py:810 msgid "Compatible" msgstr "互換モード" -#: src/iac_code/commands/auth.py:788 +#: src/iac_code/commands/auth.py:827 msgid "Select Cloud Provider" msgstr "クラウドプロバイダーを選択" -#: src/iac_code/commands/auth.py:804 +#: src/iac_code/commands/auth.py:843 msgid "Credential" msgstr "クレデンシャル" -#: src/iac_code/commands/auth.py:805 src/iac_code/commands/auth.py:902 -#: src/iac_code/commands/auth.py:1007 src/iac_code/ui/renderer.py:418 +#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:941 +#: src/iac_code/commands/auth.py:1046 src/iac_code/ui/renderer.py:418 msgid "Region" msgstr "リージョン" -#: src/iac_code/commands/auth.py:807 +#: src/iac_code/commands/auth.py:846 msgid "Configure Alibaba Cloud" msgstr "Alibaba Cloud を設定" -#: src/iac_code/commands/auth.py:890 +#: src/iac_code/commands/auth.py:929 msgid "Current configuration" msgstr "現在の設定" -#: src/iac_code/commands/auth.py:892 +#: src/iac_code/commands/auth.py:931 msgid "Mode" msgstr "モード" -#: src/iac_code/commands/auth.py:899 +#: src/iac_code/commands/auth.py:938 msgid "(not set)" msgstr "(未設定)" -#: src/iac_code/commands/auth.py:916 +#: src/iac_code/commands/auth.py:955 msgid "Configure Alibaba Cloud credentials" msgstr "Alibaba Cloud のクレデンシャルを設定" -#: src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:968 msgid "Reconfigure credential" msgstr "クレデンシャルを再設定" -#: src/iac_code/commands/auth.py:942 +#: src/iac_code/commands/auth.py:981 msgid "Select credential type" msgstr "クレデンシャルの種類を選択" -#: src/iac_code/commands/auth.py:992 +#: src/iac_code/commands/auth.py:1031 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "" "Configured: Alibaba Cloud credentials saved to ~/.iac-code設定しました:Alibaba " "Cloud のクレデンシャルを ~/.iac-code に保存しました" -#: src/iac_code/commands/auth.py:999 +#: src/iac_code/commands/auth.py:1038 msgid "Configure Alibaba Cloud region" msgstr "Alibaba Cloud のリージョンを設定" -#: src/iac_code/commands/auth.py:1025 +#: src/iac_code/commands/auth.py:1064 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "設定しました:Alibaba Cloud のリージョンを ~/.iac-code に保存しました" @@ -1552,23 +1559,23 @@ msgstr "ROS スタック" msgid "CloudStackInstances" msgstr "CloudStackInstances" -#: src/iac_code/ui/banner.py:68 +#: src/iac_code/ui/banner.py:71 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "あなたの AI 駆動の Infrastructure as Code アシスタントです" -#: src/iac_code/ui/banner.py:92 +#: src/iac_code/ui/banner.py:95 msgid "Welcome back" msgstr "おかえりなさい" -#: src/iac_code/ui/banner.py:98 +#: src/iac_code/ui/banner.py:101 msgid "Session" msgstr "セッション" -#: src/iac_code/ui/banner.py:108 +#: src/iac_code/ui/banner.py:111 msgid "Debug mode" msgstr "デバッグモード" -#: src/iac_code/ui/banner.py:109 +#: src/iac_code/ui/banner.py:112 msgid "Log file" msgstr "ログファイル" diff --git a/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po b/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po index 064cd1d4..13d17439 100644 --- a/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: iac-code 0.2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-20 12:34+0800\n" +"POT-Creation-Date: 2026-05-20 15:23+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: pt\n" @@ -537,217 +537,224 @@ msgstr "Retomar uma sessão anterior" msgid "[conversation id or search term]" msgstr "[ID da conversa ou termo de busca]" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:844 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:883 #: src/iac_code/ui/core/prompt_input.py:505 msgid "Navigate" msgstr "Navegar" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:367 -#: src/iac_code/commands/auth.py:401 src/iac_code/commands/auth.py:408 -#: src/iac_code/commands/auth.py:432 src/iac_code/commands/auth.py:844 -#: src/iac_code/commands/auth.py:1034 src/iac_code/ui/core/prompt_input.py:505 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:371 +#: src/iac_code/commands/auth.py:405 src/iac_code/commands/auth.py:412 +#: src/iac_code/commands/auth.py:436 src/iac_code/commands/auth.py:883 +#: src/iac_code/commands/auth.py:1073 src/iac_code/ui/core/prompt_input.py:505 msgid "Confirm" msgstr "Confirmar" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:365 -#: src/iac_code/commands/auth.py:367 src/iac_code/commands/auth.py:401 -#: src/iac_code/commands/auth.py:408 src/iac_code/commands/auth.py:432 -#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:929 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:369 +#: src/iac_code/commands/auth.py:371 src/iac_code/commands/auth.py:405 +#: src/iac_code/commands/auth.py:412 src/iac_code/commands/auth.py:436 +#: src/iac_code/commands/auth.py:883 src/iac_code/commands/auth.py:968 +#: src/iac_code/commands/auth.py:1073 msgid "Back" msgstr "Voltar" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Keep" msgstr "Manter" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Re-enter" msgstr "Digitar novamente" -#: src/iac_code/commands/auth.py:507 src/iac_code/commands/auth.py:638 -#: src/iac_code/commands/auth.py:645 src/iac_code/commands/auth.py:678 +#: src/iac_code/commands/auth.py:511 src/iac_code/commands/auth.py:623 +#: src/iac_code/commands/auth.py:681 src/iac_code/commands/auth.py:689 +#: src/iac_code/commands/auth.py:716 msgid " (current)" msgstr " (atual)" -#: src/iac_code/commands/auth.py:510 +#: src/iac_code/commands/auth.py:514 msgid "Custom model..." msgstr "Modelo personalizado..." -#: src/iac_code/commands/auth.py:513 +#: src/iac_code/commands/auth.py:517 #, python-brace-format msgid "Select model for {provider}" msgstr "Selecionar modelo para {provider}" -#: src/iac_code/commands/auth.py:515 +#: src/iac_code/commands/auth.py:519 msgid "Select model" msgstr "Selecionar modelo" -#: src/iac_code/commands/auth.py:523 +#: src/iac_code/commands/auth.py:527 msgid "Enter custom model name: " msgstr "Informe o nome do modelo personalizado: " -#: src/iac_code/commands/auth.py:549 +#: src/iac_code/commands/auth.py:553 msgid "Error: console not available" msgstr "Erro: console indisponível" -#: src/iac_code/commands/auth.py:576 +#: src/iac_code/commands/auth.py:580 msgid "Configure LLM Provider" msgstr "Configurar provedor LLM" -#: src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:581 msgid "Configure IaC Cloud Service" msgstr "Configurar serviço de nuvem IaC" -#: src/iac_code/commands/auth.py:579 +#: src/iac_code/commands/auth.py:583 msgid "Select configuration type" msgstr "Selecionar tipo de configuração" -#: src/iac_code/commands/auth.py:581 src/iac_code/commands/auth.py:703 -#: src/iac_code/commands/auth.py:719 src/iac_code/commands/auth.py:797 -#: src/iac_code/commands/auth.py:970 src/iac_code/commands/auth.py:1011 +#: src/iac_code/commands/auth.py:585 src/iac_code/commands/auth.py:741 +#: src/iac_code/commands/auth.py:757 src/iac_code/commands/auth.py:836 +#: src/iac_code/commands/auth.py:1009 src/iac_code/commands/auth.py:1050 msgid "Auth cancelled" msgstr "Autenticação cancelada" -#: src/iac_code/commands/auth.py:649 -msgid "Select provider" -msgstr "Selecionar provedor" +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:721 +#: src/iac_code/commands/auth.py:811 +#, python-brace-format +msgid "Select provider — {group}" +msgstr "Selecionar provedor — {group}" -#: src/iac_code/commands/auth.py:661 +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:679 +#: src/iac_code/commands/auth.py:796 +msgid "Third-party" +msgstr "Terceiros" + +#: src/iac_code/commands/auth.py:637 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:662 src/iac_code/commands/auth.py:751 +#: src/iac_code/commands/auth.py:638 src/iac_code/commands/auth.py:789 msgid "Configured" msgstr "Configurado" -#: src/iac_code/commands/auth.py:683 src/iac_code/commands/auth.py:772 -#, python-brace-format -msgid "Select provider — {group}" -msgstr "Selecionar provedor — {group}" +#: src/iac_code/commands/auth.py:693 +msgid "Select provider" +msgstr "Selecionar provedor" -#: src/iac_code/commands/auth.py:696 +#: src/iac_code/commands/auth.py:734 #, python-brace-format msgid "Configure {provider}" msgstr "Configurar {provider}" -#: src/iac_code/commands/auth.py:712 +#: src/iac_code/commands/auth.py:750 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Informe a API key para {provider}" -#: src/iac_code/commands/auth.py:750 +#: src/iac_code/commands/auth.py:788 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:758 src/iac_code/commands/auth.py:779 +#: src/iac_code/commands/auth.py:797 src/iac_code/commands/auth.py:818 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:759 src/iac_code/providers/registry.py:423 +#: src/iac_code/commands/auth.py:798 src/iac_code/providers/registry.py:423 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:760 +#: src/iac_code/commands/auth.py:799 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:761 +#: src/iac_code/commands/auth.py:800 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:762 src/iac_code/providers/registry.py:425 +#: src/iac_code/commands/auth.py:801 src/iac_code/providers/registry.py:425 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:763 +#: src/iac_code/commands/auth.py:802 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:764 src/iac_code/providers/registry.py:416 +#: src/iac_code/commands/auth.py:803 src/iac_code/providers/registry.py:416 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:765 src/iac_code/providers/registry.py:414 +#: src/iac_code/commands/auth.py:804 src/iac_code/providers/registry.py:414 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:766 src/iac_code/providers/registry.py:415 +#: src/iac_code/commands/auth.py:805 src/iac_code/providers/registry.py:415 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:767 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:806 src/iac_code/providers/registry.py:418 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:768 src/iac_code/providers/registry.py:431 +#: src/iac_code/commands/auth.py:807 src/iac_code/providers/registry.py:431 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:769 src/iac_code/providers/registry.py:430 +#: src/iac_code/commands/auth.py:808 src/iac_code/providers/registry.py:430 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:770 +#: src/iac_code/commands/auth.py:809 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:771 +#: src/iac_code/commands/auth.py:810 msgid "Compatible" msgstr "Compatível" -#: src/iac_code/commands/auth.py:788 +#: src/iac_code/commands/auth.py:827 msgid "Select Cloud Provider" msgstr "Selecionar provedor de nuvem" -#: src/iac_code/commands/auth.py:804 +#: src/iac_code/commands/auth.py:843 msgid "Credential" msgstr "Credencial" -#: src/iac_code/commands/auth.py:805 src/iac_code/commands/auth.py:902 -#: src/iac_code/commands/auth.py:1007 src/iac_code/ui/renderer.py:418 +#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:941 +#: src/iac_code/commands/auth.py:1046 src/iac_code/ui/renderer.py:418 msgid "Region" msgstr "Região" -#: src/iac_code/commands/auth.py:807 +#: src/iac_code/commands/auth.py:846 msgid "Configure Alibaba Cloud" msgstr "Configurar Alibaba Cloud" -#: src/iac_code/commands/auth.py:890 +#: src/iac_code/commands/auth.py:929 msgid "Current configuration" msgstr "Configuração atual" -#: src/iac_code/commands/auth.py:892 +#: src/iac_code/commands/auth.py:931 msgid "Mode" msgstr "Modo" -#: src/iac_code/commands/auth.py:899 +#: src/iac_code/commands/auth.py:938 msgid "(not set)" msgstr "(não definido)" -#: src/iac_code/commands/auth.py:916 +#: src/iac_code/commands/auth.py:955 msgid "Configure Alibaba Cloud credentials" msgstr "Configurar credenciais da Alibaba Cloud" -#: src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:968 msgid "Reconfigure credential" msgstr "Reconfigurar credencial" -#: src/iac_code/commands/auth.py:942 +#: src/iac_code/commands/auth.py:981 msgid "Select credential type" msgstr "Selecionar tipo de credencial" -#: src/iac_code/commands/auth.py:992 +#: src/iac_code/commands/auth.py:1031 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "Configurado: credenciais da Alibaba Cloud salvas em ~/.iac-code" -#: src/iac_code/commands/auth.py:999 +#: src/iac_code/commands/auth.py:1038 msgid "Configure Alibaba Cloud region" msgstr "Configurar região da Alibaba Cloud" -#: src/iac_code/commands/auth.py:1025 +#: src/iac_code/commands/auth.py:1064 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Configurado: região da Alibaba Cloud salva em ~/.iac-code" @@ -1574,23 +1581,23 @@ msgstr "ROS Stack" msgid "CloudStackInstances" msgstr "CloudStackInstances" -#: src/iac_code/ui/banner.py:68 +#: src/iac_code/ui/banner.py:71 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "Seu assistente de Infrastructure as Code com IA" -#: src/iac_code/ui/banner.py:92 +#: src/iac_code/ui/banner.py:95 msgid "Welcome back" msgstr "Bem-vindo de volta" -#: src/iac_code/ui/banner.py:98 +#: src/iac_code/ui/banner.py:101 msgid "Session" msgstr "Sessão" -#: src/iac_code/ui/banner.py:108 +#: src/iac_code/ui/banner.py:111 msgid "Debug mode" msgstr "Modo debug" -#: src/iac_code/ui/banner.py:109 +#: src/iac_code/ui/banner.py:112 msgid "Log file" msgstr "Arquivo de log" diff --git a/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po b/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po index 5ee1eca5..4bf7273c 100644 --- a/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: iac-code 0.2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-20 12:34+0800\n" +"POT-Creation-Date: 2026-05-20 15:23+0800\n" "PO-Revision-Date: 2026-04-02 00:00+0000\n" "Last-Translator: \n" "Language: zh\n" @@ -519,217 +519,224 @@ msgstr "恢复之前的会话" msgid "[conversation id or search term]" msgstr "[会话 ID 或搜索词]" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:844 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:883 #: src/iac_code/ui/core/prompt_input.py:505 msgid "Navigate" msgstr "导航" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:367 -#: src/iac_code/commands/auth.py:401 src/iac_code/commands/auth.py:408 -#: src/iac_code/commands/auth.py:432 src/iac_code/commands/auth.py:844 -#: src/iac_code/commands/auth.py:1034 src/iac_code/ui/core/prompt_input.py:505 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:371 +#: src/iac_code/commands/auth.py:405 src/iac_code/commands/auth.py:412 +#: src/iac_code/commands/auth.py:436 src/iac_code/commands/auth.py:883 +#: src/iac_code/commands/auth.py:1073 src/iac_code/ui/core/prompt_input.py:505 msgid "Confirm" msgstr "确认" -#: src/iac_code/commands/auth.py:245 src/iac_code/commands/auth.py:365 -#: src/iac_code/commands/auth.py:367 src/iac_code/commands/auth.py:401 -#: src/iac_code/commands/auth.py:408 src/iac_code/commands/auth.py:432 -#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:929 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:249 src/iac_code/commands/auth.py:369 +#: src/iac_code/commands/auth.py:371 src/iac_code/commands/auth.py:405 +#: src/iac_code/commands/auth.py:412 src/iac_code/commands/auth.py:436 +#: src/iac_code/commands/auth.py:883 src/iac_code/commands/auth.py:968 +#: src/iac_code/commands/auth.py:1073 msgid "Back" msgstr "返回" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Keep" msgstr "保留" -#: src/iac_code/commands/auth.py:365 +#: src/iac_code/commands/auth.py:369 msgid "Re-enter" msgstr "重新输入" -#: src/iac_code/commands/auth.py:507 src/iac_code/commands/auth.py:638 -#: src/iac_code/commands/auth.py:645 src/iac_code/commands/auth.py:678 +#: src/iac_code/commands/auth.py:511 src/iac_code/commands/auth.py:623 +#: src/iac_code/commands/auth.py:681 src/iac_code/commands/auth.py:689 +#: src/iac_code/commands/auth.py:716 msgid " (current)" msgstr " (当前)" -#: src/iac_code/commands/auth.py:510 +#: src/iac_code/commands/auth.py:514 msgid "Custom model..." msgstr "自定义模型..." -#: src/iac_code/commands/auth.py:513 +#: src/iac_code/commands/auth.py:517 #, python-brace-format msgid "Select model for {provider}" msgstr "为 {provider} 选择模型" -#: src/iac_code/commands/auth.py:515 +#: src/iac_code/commands/auth.py:519 msgid "Select model" msgstr "选择模型" -#: src/iac_code/commands/auth.py:523 +#: src/iac_code/commands/auth.py:527 msgid "Enter custom model name: " msgstr "输入自定义模型名称:" -#: src/iac_code/commands/auth.py:549 +#: src/iac_code/commands/auth.py:553 msgid "Error: console not available" msgstr "错误:控制台不可用" -#: src/iac_code/commands/auth.py:576 +#: src/iac_code/commands/auth.py:580 msgid "Configure LLM Provider" msgstr "配置 LLM 提供商" -#: src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:581 msgid "Configure IaC Cloud Service" msgstr "配置 IaC 云服务" -#: src/iac_code/commands/auth.py:579 +#: src/iac_code/commands/auth.py:583 msgid "Select configuration type" msgstr "选择配置类型" -#: src/iac_code/commands/auth.py:581 src/iac_code/commands/auth.py:703 -#: src/iac_code/commands/auth.py:719 src/iac_code/commands/auth.py:797 -#: src/iac_code/commands/auth.py:970 src/iac_code/commands/auth.py:1011 +#: src/iac_code/commands/auth.py:585 src/iac_code/commands/auth.py:741 +#: src/iac_code/commands/auth.py:757 src/iac_code/commands/auth.py:836 +#: src/iac_code/commands/auth.py:1009 src/iac_code/commands/auth.py:1050 msgid "Auth cancelled" msgstr "认证已取消" -#: src/iac_code/commands/auth.py:649 -msgid "Select provider" -msgstr "选择提供商" +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:721 +#: src/iac_code/commands/auth.py:811 +#, python-brace-format +msgid "Select provider — {group}" +msgstr "选择提供商 — {group}" -#: src/iac_code/commands/auth.py:661 +#: src/iac_code/commands/auth.py:627 src/iac_code/commands/auth.py:679 +#: src/iac_code/commands/auth.py:796 +msgid "Third-party" +msgstr "第三方" + +#: src/iac_code/commands/auth.py:637 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}:{provider}" -#: src/iac_code/commands/auth.py:662 src/iac_code/commands/auth.py:751 +#: src/iac_code/commands/auth.py:638 src/iac_code/commands/auth.py:789 msgid "Configured" msgstr "已配置" -#: src/iac_code/commands/auth.py:683 src/iac_code/commands/auth.py:772 -#, python-brace-format -msgid "Select provider — {group}" -msgstr "选择提供商 — {group}" +#: src/iac_code/commands/auth.py:693 +msgid "Select provider" +msgstr "选择提供商" -#: src/iac_code/commands/auth.py:696 +#: src/iac_code/commands/auth.py:734 #, python-brace-format msgid "Configure {provider}" msgstr "配置 {provider}" -#: src/iac_code/commands/auth.py:712 +#: src/iac_code/commands/auth.py:750 #, python-brace-format msgid "Enter API key for {provider}" msgstr "为 {provider} 输入 API 密钥" -#: src/iac_code/commands/auth.py:750 +#: src/iac_code/commands/auth.py:788 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}:{provider} / {model}" -#: src/iac_code/commands/auth.py:758 src/iac_code/commands/auth.py:779 +#: src/iac_code/commands/auth.py:797 src/iac_code/commands/auth.py:818 msgid "Alibaba Cloud" msgstr "阿里云" -#: src/iac_code/commands/auth.py:759 src/iac_code/providers/registry.py:423 +#: src/iac_code/commands/auth.py:798 src/iac_code/providers/registry.py:423 msgid "ZhiPu AI" msgstr "智谱 AI" -#: src/iac_code/commands/auth.py:760 +#: src/iac_code/commands/auth.py:799 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:761 +#: src/iac_code/commands/auth.py:800 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:762 src/iac_code/providers/registry.py:425 +#: src/iac_code/commands/auth.py:801 src/iac_code/providers/registry.py:425 msgid "Volcengine" msgstr "火山引擎" -#: src/iac_code/commands/auth.py:763 +#: src/iac_code/commands/auth.py:802 msgid "SiliconFlow" msgstr "硅基流动" -#: src/iac_code/commands/auth.py:764 src/iac_code/providers/registry.py:416 +#: src/iac_code/commands/auth.py:803 src/iac_code/providers/registry.py:416 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:765 src/iac_code/providers/registry.py:414 +#: src/iac_code/commands/auth.py:804 src/iac_code/providers/registry.py:414 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:766 src/iac_code/providers/registry.py:415 +#: src/iac_code/commands/auth.py:805 src/iac_code/providers/registry.py:415 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:767 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:806 src/iac_code/providers/registry.py:418 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:768 src/iac_code/providers/registry.py:431 +#: src/iac_code/commands/auth.py:807 src/iac_code/providers/registry.py:431 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:769 src/iac_code/providers/registry.py:430 +#: src/iac_code/commands/auth.py:808 src/iac_code/providers/registry.py:430 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:770 +#: src/iac_code/commands/auth.py:809 msgid "Local" msgstr "本地模型" -#: src/iac_code/commands/auth.py:771 +#: src/iac_code/commands/auth.py:810 msgid "Compatible" msgstr "兼容模式" -#: src/iac_code/commands/auth.py:788 +#: src/iac_code/commands/auth.py:827 msgid "Select Cloud Provider" msgstr "选择云服务商" -#: src/iac_code/commands/auth.py:804 +#: src/iac_code/commands/auth.py:843 msgid "Credential" msgstr "凭证" -#: src/iac_code/commands/auth.py:805 src/iac_code/commands/auth.py:902 -#: src/iac_code/commands/auth.py:1007 src/iac_code/ui/renderer.py:418 +#: src/iac_code/commands/auth.py:844 src/iac_code/commands/auth.py:941 +#: src/iac_code/commands/auth.py:1046 src/iac_code/ui/renderer.py:418 msgid "Region" msgstr "地域" -#: src/iac_code/commands/auth.py:807 +#: src/iac_code/commands/auth.py:846 msgid "Configure Alibaba Cloud" msgstr "配置阿里云" -#: src/iac_code/commands/auth.py:890 +#: src/iac_code/commands/auth.py:929 msgid "Current configuration" msgstr "当前配置" -#: src/iac_code/commands/auth.py:892 +#: src/iac_code/commands/auth.py:931 msgid "Mode" msgstr "模式" -#: src/iac_code/commands/auth.py:899 +#: src/iac_code/commands/auth.py:938 msgid "(not set)" msgstr "(未设置)" -#: src/iac_code/commands/auth.py:916 +#: src/iac_code/commands/auth.py:955 msgid "Configure Alibaba Cloud credentials" msgstr "配置阿里云凭证" -#: src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:968 msgid "Reconfigure credential" msgstr "重新配置凭证" -#: src/iac_code/commands/auth.py:942 +#: src/iac_code/commands/auth.py:981 msgid "Select credential type" msgstr "选择凭证类型" -#: src/iac_code/commands/auth.py:992 +#: src/iac_code/commands/auth.py:1031 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "已配置:阿里云凭证已保存到 ~/.iac-code" -#: src/iac_code/commands/auth.py:999 +#: src/iac_code/commands/auth.py:1038 msgid "Configure Alibaba Cloud region" msgstr "配置阿里云地域" -#: src/iac_code/commands/auth.py:1025 +#: src/iac_code/commands/auth.py:1064 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "已配置:阿里云地域已保存到 ~/.iac-code" @@ -1539,23 +1546,23 @@ msgstr "ROS 资源栈" msgid "CloudStackInstances" msgstr "云资源栈实例" -#: src/iac_code/ui/banner.py:68 +#: src/iac_code/ui/banner.py:71 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "您的 AI 驱动的基础设施即代码助手" -#: src/iac_code/ui/banner.py:92 +#: src/iac_code/ui/banner.py:95 msgid "Welcome back" msgstr "欢迎回来" -#: src/iac_code/ui/banner.py:98 +#: src/iac_code/ui/banner.py:101 msgid "Session" msgstr "会话" -#: src/iac_code/ui/banner.py:108 +#: src/iac_code/ui/banner.py:111 msgid "Debug mode" msgstr "调试模式" -#: src/iac_code/ui/banner.py:109 +#: src/iac_code/ui/banner.py:112 msgid "Log file" msgstr "日志文件" diff --git a/src/iac_code/ui/banner.py b/src/iac_code/ui/banner.py index 67bf9c06..cb1a667a 100644 --- a/src/iac_code/ui/banner.py +++ b/src/iac_code/ui/banner.py @@ -36,8 +36,11 @@ def _get_provider_display() -> str: if not key: llm_source = get_llm_source() for ps in PARTNER_SOURCES: - if ps["key"] == llm_source: - return ps["display_name"] + if ps.key == llm_source: + real_provider = ps.get_provider_display() + if real_provider: + return "{} / {}".format(ps.display_name, real_provider) + return ps.display_name return "" desc = PROVIDER_REGISTRY.get(key) if desc: diff --git a/tests/commands/test_auth_flows.py b/tests/commands/test_auth_flows.py index 77ab90c5..b4e5f9f5 100644 --- a/tests/commands/test_auth_flows.py +++ b/tests/commands/test_auth_flows.py @@ -139,7 +139,7 @@ def test_back_at_sub_provider_returns_to_group(self, monkeypatch): def select_side_effect(title, options, default_index=0): call_count["select"] += 1 if call_count["select"] == 1: - return 1 # Alibaba Cloud group (multiple items), after partner sources + return 0 # Alibaba Cloud group (first entry when no third-party) if call_count["select"] == 2: return None # Esc at sub-provider → back to group return None # Esc at group → _BACK @@ -155,7 +155,7 @@ def test_back_at_api_key_input_loops_to_group_select(self, monkeypatch): def select_side_effect(title, options, default_index=0): call_count["select"] += 1 if call_count["select"] == 1: - return 1 # Alibaba Cloud group (after partner sources) + return 0 # Alibaba Cloud group (first entry when no third-party) if call_count["select"] == 2: return 0 # sub-provider return None # Esc → _BACK @@ -168,13 +168,13 @@ def select_side_effect(title, options, default_index=0): assert call_count["select"] == 3 def test_cancel_at_api_key_input_returns_cancelled(self, monkeypatch): - # _select returns 1 for group (Alibaba Cloud, after partner sources) then 0 for sub-provider + # _select returns 0 for group (Alibaba Cloud, first entry when no third-party) then 0 for sub-provider call_count = {"n": 0} def select_side_effect(title, options, default_index=0): call_count["n"] += 1 if call_count["n"] == 1: - return 1 # Alibaba Cloud group (after partner sources) + return 0 # Alibaba Cloud group (first entry when no third-party) return 0 # DashScope provider monkeypatch.setattr("iac_code.commands.auth._select", select_side_effect) @@ -206,13 +206,13 @@ def select_side_effect(title, options, default_index=0): assert call_count["select"] == 2 # group + group again (no sub-provider step) def test_successful_config_saves_and_returns_string(self, monkeypatch): - # _select returns 1 for group (Alibaba Cloud, after partner sources) then 0 for sub-provider + # _select returns 0 for group (Alibaba Cloud, first entry when no third-party) then 0 for sub-provider call_count = {"n": 0} def select_side_effect(title, options, default_index=0): call_count["n"] += 1 if call_count["n"] == 1: - return 1 # Alibaba Cloud group (after partner sources) + return 0 # Alibaba Cloud group (first entry when no third-party) return 0 # DashScope (first sub-provider) monkeypatch.setattr("iac_code.commands.auth._select", select_side_effect) @@ -414,8 +414,10 @@ def fake_select(title, options, default_index=0): class TestPartnerSourceInLlmFlow: - def test_partner_source_shown_at_top_of_list(self, monkeypatch): - """QwenPaw should appear as the first option in provider group selection.""" + def test_third_party_shown_at_top_when_partner_available(self, monkeypatch): + """'Third-party' should appear as the first option when partners are available.""" + from iac_code.config import PartnerSource + options_seen = [] def fake_select(title, options, default_index=0): @@ -424,14 +426,19 @@ def fake_select(title, options, default_index=0): monkeypatch.setattr("iac_code.commands.auth._select", fake_select) monkeypatch.setattr("iac_code.commands.auth.get_active_provider_key", lambda: None) + monkeypatch.setattr( + "iac_code.commands.auth.get_available_partner_sources", + lambda: [PartnerSource(key="qwenpaw", display_name="QwenPaw")], + ) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "local") result = _llm_auth_flow(MagicMock(), MagicMock()) assert result is _BACK assert len(options_seen) > 0 - assert "QwenPaw" in options_seen[0] + assert "Third-party" in options_seen[0] - def test_selecting_partner_source_clears_active_provider(self, monkeypatch, iac_home): - """Selecting QwenPaw clears activeProvider and sets llm_source.""" - from iac_code.config import _load_yaml + def test_selecting_third_party_clears_active_provider(self, monkeypatch, iac_home): + """Selecting Third-party → QwenPaw clears activeProvider and sets llm_source.""" + from iac_code.config import PartnerSource, _load_yaml settings_path = iac_home / ".iac-code" / "settings.yml" settings_path.parent.mkdir(parents=True, exist_ok=True) @@ -439,16 +446,19 @@ def test_selecting_partner_source_clears_active_provider(self, monkeypatch, iac_ monkeypatch.setattr("iac_code.commands.auth.get_settings_path", lambda: settings_path) - call_count = {"n": 0} - def fake_select(title, options, default_index=0): - call_count["n"] += 1 - if call_count["n"] == 1: - return 0 # Select first option (QwenPaw) - return None + return 0 # Select Third-party, then single partner auto-selects monkeypatch.setattr("iac_code.commands.auth._select", fake_select) monkeypatch.setattr("iac_code.commands.auth.get_active_provider_key", lambda: "dashscope") + monkeypatch.setattr( + "iac_code.commands.auth.get_available_partner_sources", + lambda: [PartnerSource(key="qwenpaw", display_name="QwenPaw")], + ) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "local") + monkeypatch.setattr( + "iac_code.commands.auth.PARTNER_SOURCES", [PartnerSource(key="qwenpaw", display_name="QwenPaw")] + ) _llm_auth_flow(MagicMock(), MagicMock()) config = _load_yaml(settings_path) @@ -457,8 +467,10 @@ def fake_select(title, options, default_index=0): # providers dict preserved assert "dashscope" in config.get("providers", {}) - def test_selecting_partner_source_returns_configured_message(self, monkeypatch, iac_home): - """Selecting QwenPaw returns a success message.""" + def test_selecting_third_party_returns_configured_message(self, monkeypatch, iac_home): + """Selecting Third-party → QwenPaw returns a success message.""" + from iac_code.config import PartnerSource + settings_path = iac_home / ".iac-code" / "settings.yml" settings_path.parent.mkdir(parents=True, exist_ok=True) settings_path.write_text("") @@ -466,10 +478,18 @@ def test_selecting_partner_source_returns_configured_message(self, monkeypatch, monkeypatch.setattr("iac_code.commands.auth.get_settings_path", lambda: settings_path) def fake_select(title, options, default_index=0): - return 0 # Select QwenPaw + return 0 # Select Third-party, then single partner auto-selects monkeypatch.setattr("iac_code.commands.auth._select", fake_select) monkeypatch.setattr("iac_code.commands.auth.get_active_provider_key", lambda: None) + monkeypatch.setattr( + "iac_code.commands.auth.get_available_partner_sources", + lambda: [PartnerSource(key="qwenpaw", display_name="QwenPaw")], + ) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "local") + monkeypatch.setattr( + "iac_code.commands.auth.PARTNER_SOURCES", [PartnerSource(key="qwenpaw", display_name="QwenPaw")] + ) result = _llm_auth_flow(MagicMock(), MagicMock()) assert isinstance(result, str) diff --git a/tests/commands/test_auth_third_party.py b/tests/commands/test_auth_third_party.py new file mode 100644 index 00000000..931d8036 --- /dev/null +++ b/tests/commands/test_auth_third_party.py @@ -0,0 +1,137 @@ +"""Tests for third-party category in /auth LLM flow.""" + +from unittest.mock import MagicMock + +from iac_code.commands.auth import _BACK, _llm_auth_flow +from iac_code.config import PartnerSource + + +class TestThirdPartyCategory: + def test_third_party_shown_when_partner_available(self, monkeypatch): + """When at least one partner source is available, 'Third-party' appears in group options.""" + options_seen = [] + + def fake_select(title, options, default_index=0): + options_seen.extend(options) + return None # Esc + + available = [PartnerSource(key="qwenpaw", display_name="QwenPaw")] + monkeypatch.setattr("iac_code.commands.auth.get_available_partner_sources", lambda: available) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "local") + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + + result = _llm_auth_flow(MagicMock(), MagicMock()) + assert result is _BACK + assert any("Third-party" in opt for opt in options_seen) + + def test_third_party_hidden_when_no_partner_available(self, monkeypatch): + """When no partner sources are available and llm_source is 'local', no 'Third-party' entry.""" + options_seen = [] + + def fake_select(title, options, default_index=0): + options_seen.extend(options) + return None # Esc + + monkeypatch.setattr("iac_code.commands.auth.get_available_partner_sources", lambda: []) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "local") + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + + result = _llm_auth_flow(MagicMock(), MagicMock()) + assert result is _BACK + assert not any("Third-party" in opt for opt in options_seen) + + def test_third_party_shown_when_llm_source_is_partner(self, monkeypatch): + """When llm_source matches a partner key, 'Third-party' appears even if detection fails.""" + options_seen = [] + + def fake_select(title, options, default_index=0): + options_seen.extend(options) + return None # Esc + + monkeypatch.setattr("iac_code.commands.auth.get_available_partner_sources", lambda: []) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "qwenpaw") + monkeypatch.setattr( + "iac_code.commands.auth.PARTNER_SOURCES", [PartnerSource(key="qwenpaw", display_name="QwenPaw")] + ) + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + + result = _llm_auth_flow(MagicMock(), MagicMock()) + assert result is _BACK + assert any("Third-party" in opt for opt in options_seen) + + def test_third_party_marked_current_when_partner_active(self, monkeypatch): + """When current llm_source is a partner key, 'Third-party' shows '(current)'.""" + options_seen = [] + + def fake_select(title, options, default_index=0): + options_seen.extend(options) + return None + + monkeypatch.setattr( + "iac_code.commands.auth.get_available_partner_sources", + lambda: [PartnerSource(key="qwenpaw", display_name="QwenPaw")], + ) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "qwenpaw") + monkeypatch.setattr( + "iac_code.commands.auth.PARTNER_SOURCES", [PartnerSource(key="qwenpaw", display_name="QwenPaw")] + ) + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + + result = _llm_auth_flow(MagicMock(), MagicMock()) + assert result is _BACK + third_party_opt = [opt for opt in options_seen if "Third-party" in opt] + assert len(third_party_opt) == 1 + assert "(current)" in third_party_opt[0] + + def test_selecting_third_party_enters_sub_flow(self, monkeypatch, tmp_path): + """Selecting 'Third-party' enters the sub-flow and configures the partner.""" + monkeypatch.setenv("HOME", str(tmp_path)) + settings_path = tmp_path / ".iac-code" / "settings.yml" + settings_path.parent.mkdir(parents=True, exist_ok=True) + settings_path.write_text("") + + monkeypatch.setattr("iac_code.commands.auth.get_settings_path", lambda: settings_path) + monkeypatch.setattr( + "iac_code.commands.auth.get_available_partner_sources", + lambda: [PartnerSource(key="qwenpaw", display_name="QwenPaw")], + ) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "local") + monkeypatch.setattr( + "iac_code.commands.auth.PARTNER_SOURCES", [PartnerSource(key="qwenpaw", display_name="QwenPaw")] + ) + + def fake_select(title, options, default_index=0): + return 0 # Select first option (Third-party, then the single partner) + + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + monkeypatch.setattr("iac_code.commands.auth.get_active_provider_key", lambda: None) + + result = _llm_auth_flow(MagicMock(), MagicMock()) + assert isinstance(result, str) + assert "QwenPaw" in result + + def test_back_from_third_party_sub_flow_returns_to_group(self, monkeypatch): + """Pressing Esc in third-party sub-flow returns to group selection.""" + call_count = {"select": 0} + + def fake_select(title, options, default_index=0): + call_count["select"] += 1 + if call_count["select"] == 1: + return 0 # Select Third-party + if call_count["select"] == 2: + return None # Esc in sub-flow + return None # Esc at group level + + monkeypatch.setattr( + "iac_code.commands.auth.get_available_partner_sources", + lambda: [ + PartnerSource(key="partner_a", display_name="PartnerA"), + PartnerSource(key="partner_b", display_name="PartnerB"), + ], + ) + monkeypatch.setattr("iac_code.commands.auth.get_llm_source", lambda: "local") + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + + result = _llm_auth_flow(MagicMock(), MagicMock()) + assert result is _BACK + assert call_count["select"] == 3 diff --git a/tests/test_config.py b/tests/test_config.py index 53082f7f..ac234a80 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,9 @@ from __future__ import annotations -from unittest.mock import patch +from unittest.mock import MagicMock, patch + +from iac_code.config import PARTNER_SOURCES, PartnerSource, get_available_partner_sources class TestConfigPaths: @@ -229,3 +231,65 @@ def test_save_yaml_creates_parent_dirs(self, tmp_path): _save_yaml(path, {"key": "value"}) assert path.exists() + + +"""Tests for PartnerSource dataclass.""" + + +class TestPartnerSource: + def test_partner_source_attributes(self): + ps = PartnerSource(key="qwenpaw", display_name="QwenPaw") + assert ps.key == "qwenpaw" + assert ps.display_name == "QwenPaw" + + def test_is_available_qwenpaw_secret_dir_exists(self): + ps = PartnerSource(key="qwenpaw", display_name="QwenPaw") + with patch("iac_code.services.qwenpaw_source._resolve_secret_dir", return_value="/fake/dir"): + assert ps.is_available() is True + + def test_is_available_qwenpaw_secret_dir_missing(self): + ps = PartnerSource(key="qwenpaw", display_name="QwenPaw") + with patch("iac_code.services.qwenpaw_source._resolve_secret_dir", return_value=None): + assert ps.is_available() is False + + def test_is_available_unknown_key(self): + ps = PartnerSource(key="unknown", display_name="Unknown") + assert ps.is_available() is False + + def test_get_provider_display_qwenpaw(self): + ps = PartnerSource(key="qwenpaw", display_name="QwenPaw") + mock_config = MagicMock() + mock_config.provider_key = "dashscope" + with patch("iac_code.services.qwenpaw_source.load_from_qwenpaw", return_value=mock_config): + result = ps.get_provider_display() + assert result == "Alibaba Cloud Bailian" + + def test_get_provider_display_qwenpaw_no_config(self): + ps = PartnerSource(key="qwenpaw", display_name="QwenPaw") + with patch("iac_code.services.qwenpaw_source.load_from_qwenpaw", return_value=None): + result = ps.get_provider_display() + assert result == "" + + def test_get_provider_display_qwenpaw_exception(self): + ps = PartnerSource(key="qwenpaw", display_name="QwenPaw") + with patch("iac_code.services.qwenpaw_source.load_from_qwenpaw", side_effect=RuntimeError("fail")): + result = ps.get_provider_display() + assert result == "" + + def test_get_provider_display_unknown_key(self): + ps = PartnerSource(key="unknown", display_name="Unknown") + assert ps.get_provider_display() == "" + + def test_partner_sources_list_contains_qwenpaw(self): + assert any(ps.key == "qwenpaw" for ps in PARTNER_SOURCES) + + def test_get_available_partner_sources_with_available(self): + with patch("iac_code.services.qwenpaw_source._resolve_secret_dir", return_value="/fake"): + result = get_available_partner_sources() + assert len(result) >= 1 + assert any(ps.key == "qwenpaw" for ps in result) + + def test_get_available_partner_sources_none_available(self): + with patch("iac_code.services.qwenpaw_source._resolve_secret_dir", return_value=None): + result = get_available_partner_sources() + assert len(result) == 0 diff --git a/tests/ui/test_banner.py b/tests/ui/test_banner.py index 36cfe21e..6e2fa20e 100644 --- a/tests/ui/test_banner.py +++ b/tests/ui/test_banner.py @@ -136,6 +136,42 @@ def test_dashscope_token_plan_display_from_registry(self): result = banner._get_provider_display() assert result == "Alibaba Cloud Bailian Token Plan" + def test_qwenpaw_source_shows_real_provider(self): + """When llm_source is qwenpaw, display includes real provider name.""" + from unittest.mock import MagicMock + + mock_config = MagicMock() + mock_config.provider_key = "dashscope" + + with ( + patch("iac_code.config.get_active_provider_key", return_value=None), + patch("iac_code.config.get_llm_source", return_value="qwenpaw"), + patch("iac_code.services.qwenpaw_source.load_from_qwenpaw", return_value=mock_config), + ): + result = self._call() + assert "QwenPaw" in result + assert "Alibaba Cloud Bailian" in result + + def test_qwenpaw_source_fallback_on_error(self): + """When QwenPaw config fails, display falls back to just 'QwenPaw'.""" + with ( + patch("iac_code.config.get_active_provider_key", return_value=None), + patch("iac_code.config.get_llm_source", return_value="qwenpaw"), + patch("iac_code.services.qwenpaw_source.load_from_qwenpaw", side_effect=RuntimeError("fail")), + ): + result = self._call() + assert result == "QwenPaw" + + def test_qwenpaw_source_no_config_fallback(self): + """When QwenPaw returns None config, display falls back to just 'QwenPaw'.""" + with ( + patch("iac_code.config.get_active_provider_key", return_value=None), + patch("iac_code.config.get_llm_source", return_value="qwenpaw"), + patch("iac_code.services.qwenpaw_source.load_from_qwenpaw", return_value=None), + ): + result = self._call() + assert result == "QwenPaw" + # --------------------------------------------------------------------------- # render_welcome_banner