From 7f13e5a43225a26528f17529153e1b4981a64752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 11:01:28 +0800 Subject: [PATCH 01/11] feat: add memory command --- src/iac_code/acp/server.py | 32 +++++- src/iac_code/acp/session.py | 8 +- src/iac_code/acp/slash_registry.py | 15 ++- src/iac_code/commands/__init__.py | 10 ++ src/iac_code/commands/memory.py | 85 ++++++++++++++ .../i18n/locales/de/LC_MESSAGES/messages.po | 108 ++++++++++++++---- .../i18n/locales/es/LC_MESSAGES/messages.po | 108 ++++++++++++++---- .../i18n/locales/fr/LC_MESSAGES/messages.po | 108 ++++++++++++++---- .../i18n/locales/ja/LC_MESSAGES/messages.po | 108 ++++++++++++++---- .../i18n/locales/pt/LC_MESSAGES/messages.po | 108 ++++++++++++++---- .../i18n/locales/zh/LC_MESSAGES/messages.po | 108 ++++++++++++++---- src/iac_code/memory/memory_manager.py | 87 +++++++++++--- src/iac_code/memory/memory_tools.py | 7 +- src/iac_code/ui/repl.py | 2 +- .../ui/suggestions/command_provider.py | 94 ++++++++++++++- .../ui/suggestions/token_extractor.py | 20 ++++ tests/acp/test_sessions.py | 18 +++ tests/acp/test_slash_registry.py | 77 +++++++++++++ tests/commands/test_memory.py | 89 +++++++++++++++ tests/commands/test_registry.py | 19 ++- tests/memory/test_memory_manager.py | 65 +++++++++++ tests/memory/test_memory_tools.py | 7 ++ tests/test_i18n.py | 42 +++++++ tests/ui/suggestions/test_aggregator.py | 23 ++++ tests/ui/suggestions/test_command_provider.py | 51 +++++++++ tests/ui/suggestions/test_token_extractor.py | 20 ++++ 26 files changed, 1239 insertions(+), 180 deletions(-) create mode 100644 src/iac_code/commands/memory.py create mode 100644 tests/commands/test_memory.py diff --git a/src/iac_code/acp/server.py b/src/iac_code/acp/server.py index 02c9f9f..729e4a4 100644 --- a/src/iac_code/acp/server.py +++ b/src/iac_code/acp/server.py @@ -152,7 +152,12 @@ async def new_session( runtime.session_id, ) session = ACPSession( - runtime.session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics + runtime.session_id, + runtime.agent_loop, + self.conn, + mcp_configs=mcp_configs, + metrics=self.metrics, + memory_manager=getattr(runtime, "memory_manager", None), ) self.sessions[session.id] = session self.metrics.record_session_created() @@ -297,7 +302,14 @@ async def load_session( runtime.agent_loop.context_manager.load_messages(history) # 4. Register session - session = ACPSession(session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics) + session = ACPSession( + session_id, + runtime.agent_loop, + self.conn, + mcp_configs=mcp_configs, + metrics=self.metrics, + memory_manager=getattr(runtime, "memory_manager", None), + ) self.sessions[session_id] = session self.metrics.record_session_created() logger.info("Session loaded, session_id=%s, history_messages=%d", session_id, len(history)) @@ -360,7 +372,12 @@ async def fork_session( # 4. Register the forked session session = ACPSession( - new_session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics + new_session_id, + runtime.agent_loop, + self.conn, + mcp_configs=mcp_configs, + metrics=self.metrics, + memory_manager=getattr(runtime, "memory_manager", None), ) self.sessions[new_session_id] = session self.metrics.record_session_created() @@ -418,7 +435,14 @@ async def resume_session( runtime.agent_loop.context_manager.load_messages(history) # 4. Register the resumed session - session = ACPSession(session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics) + session = ACPSession( + session_id, + runtime.agent_loop, + self.conn, + mcp_configs=mcp_configs, + metrics=self.metrics, + memory_manager=getattr(runtime, "memory_manager", None), + ) self.sessions[session_id] = session self.metrics.record_session_created() await self._push_available_commands(session_id) diff --git a/src/iac_code/acp/session.py b/src/iac_code/acp/session.py index 9761ddc..adc717b 100644 --- a/src/iac_code/acp/session.py +++ b/src/iac_code/acp/session.py @@ -165,9 +165,11 @@ def __init__( conn: acp.Client, mcp_configs: list[dict] | None = None, metrics: ACPMetrics | None = None, + memory_manager=None, ) -> None: self.id = session_id self.agent_loop = agent_loop + self.memory_manager = memory_manager self._conn = conn self._current_task: asyncio.Task | None = None self._replay_task: asyncio.Task[None] | None = None @@ -292,7 +294,11 @@ async def prompt(self, prompt: list[ACPContentBlock]) -> acp.PromptResponse: prompt_text = acp_blocks_to_prompt_text(prompt) slash_registry = ACPSlashRegistry() if slash_registry.is_slash_command(prompt_text): - result = await slash_registry.execute(prompt_text, self.agent_loop) + result = await slash_registry.execute( + prompt_text, + self.agent_loop, + memory_manager=self.memory_manager, + ) await self._conn.session_update( session_id=self.id, update=acp.schema.AgentMessageChunk( diff --git a/src/iac_code/acp/slash_registry.py b/src/iac_code/acp/slash_registry.py index 24ca565..1646553 100644 --- a/src/iac_code/acp/slash_registry.py +++ b/src/iac_code/acp/slash_registry.py @@ -1,7 +1,7 @@ """ACP slash command registry. Manages commands supported over the ACP protocol. -Only /compact, /clear, and /debug are allowed; +Only /compact, /clear, /debug, and /memory are allowed; all other slash commands are rejected with a clear message. """ @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) -ACP_SUPPORTED_COMMANDS: frozenset[str] = frozenset({"compact", "clear", "debug"}) +ACP_SUPPORTED_COMMANDS: frozenset[str] = frozenset({"compact", "clear", "debug", "memory"}) class ACPSlashRegistry: @@ -51,6 +51,8 @@ async def execute(self, text: str, agent_loop, **context) -> str: return await self._handle_clear(agent_loop) if cmd_name == "debug": return self._handle_debug(args_str) + if cmd_name == "memory": + return self._handle_memory(args_str, context.get("memory_manager")) # Should not reach here return _("Command '/{cmd_name}' handler not implemented.").format(cmd_name=cmd_name) # pragma: no cover @@ -123,3 +125,12 @@ def _handle_debug(self, args: str) -> str: return _("Debug logging disabled.") return _("Usage: /debug [on|off]") + + def _handle_memory(self, args: str, memory_manager) -> str: + """View and manage persistent memories.""" + if memory_manager is None: + return _("Memory manager is unavailable.") + + from iac_code.commands.memory import execute_memory_command + + return execute_memory_command(memory_manager, args.split()) diff --git a/src/iac_code/commands/__init__.py b/src/iac_code/commands/__init__.py index 88aafdf..8bbdc49 100644 --- a/src/iac_code/commands/__init__.py +++ b/src/iac_code/commands/__init__.py @@ -7,6 +7,7 @@ from iac_code.commands.effort import effort_command from iac_code.commands.exit import exit_command from iac_code.commands.help import help_command +from iac_code.commands.memory import memory_command from iac_code.commands.model import model_command from iac_code.commands.registry import Command, CommandRegistry, LocalCommand, PromptCommand from iac_code.commands.resume import resume_command @@ -87,6 +88,15 @@ def create_default_registry() -> CommandRegistry: history_mode="session", ) ) + registry.register( + LocalCommand( + name="memory", + description=_("View and manage persistent memories"), + handler=memory_command, + arg_hint=_("[|search |delete |help]"), + history_mode="session", + ) + ) registry.register( LocalCommand( name="resume", diff --git a/src/iac_code/commands/memory.py b/src/iac_code/commands/memory.py new file mode 100644 index 0000000..402f14a --- /dev/null +++ b/src/iac_code/commands/memory.py @@ -0,0 +1,85 @@ +"""Memory command - view and manage persistent memories.""" + +from __future__ import annotations + +from typing import Any + +from iac_code.i18n import _ +from iac_code.memory.memory_manager import MemoryManager + +MEMORY_USAGE = _("Usage: /memory [|search |delete |help]") +_RESERVED_SUBCOMMANDS = {"search", "delete", "help"} + + +def _format_summary(title: str, memories: list[dict[str, Any]]) -> str: + if not memories: + return "" + + lines = [title] + for memory in sorted(memories, key=lambda item: str(item.get("name", ""))): + lines.append( + " - {name} - {description}".format( + name=memory.get("name", ""), + description=memory.get("description", ""), + ) + ) + return "\n".join(lines) + + +def _format_memory(memory: dict[str, Any]) -> str: + return "[{type}] {description}\n\n{content}".format( + type=memory.get("type", ""), + description=memory.get("description", ""), + content=memory.get("content", ""), + ) + + +def execute_memory_command(memory_manager: MemoryManager, args: list[str]) -> str: + if not args: + memories = memory_manager.list_memories() + return _format_summary(_("Saved memories:"), memories) or _("No memories saved yet.") + + action = args[0].lower() + if action == "help": + return MEMORY_USAGE + + if action == "search": + query = " ".join(args[1:]).strip() + if not query: + return MEMORY_USAGE + matches = memory_manager.search(query) + return _format_summary(_("Matching memories:"), matches) or _("No matching memories.") + + if action == "delete": + if len(args) != 2: + return MEMORY_USAGE + name = args[1] + try: + existing = memory_manager.load(name) + if existing is None: + return _("Memory '{name}' not found.").format(name=name) + memory_manager.delete(name) + except ValueError as exc: + return str(exc) + return _("Memory '{name}' deleted.").format(name=name) + + if len(args) != 1 or action in _RESERVED_SUBCOMMANDS: + return MEMORY_USAGE + + name = args[0] + try: + memory = memory_manager.load(name) + except ValueError as exc: + return str(exc) + if memory is None: + return _("Memory '{name}' not found.").format(name=name) + return _format_memory(memory) + + +async def memory_command(**kwargs) -> str: + context = kwargs.get("context") + repl = getattr(context, "repl", None) if context is not None else None + memory_manager = getattr(repl, "_memory_manager", None) + if memory_manager is None: + return _("Memory manager is unavailable.") + return execute_memory_command(memory_manager, kwargs.get("args") or []) 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 ae5fdbb..3dd4fac 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.3.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-01 17:56+0800\n" +"POT-Creation-Date: 2026-06-02 11:24+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: de\n" @@ -41,21 +41,21 @@ msgstr "" "Der Befehl '/{cmd_name}' wird über ACP nicht unterstützt. Unterstützte " "Befehle: {supported}" -#: src/iac_code/acp/slash_registry.py:56 +#: src/iac_code/acp/slash_registry.py:58 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "Der Handler für den Befehl '/{cmd_name}' ist nicht implementiert." -#: src/iac_code/acp/slash_registry.py:68 +#: src/iac_code/acp/slash_registry.py:70 #, python-brace-format msgid "Compaction failed: {error}" msgstr "Komprimierung fehlgeschlagen: {error}" -#: src/iac_code/acp/slash_registry.py:71 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "Nichts zu komprimieren: Die Konversation ist leer." -#: src/iac_code/acp/slash_registry.py:74 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format msgid "" "Conversation too short to compact: all messages are within the recent " @@ -64,11 +64,11 @@ msgstr "" "Konversation zu kurz zum Komprimieren: Alle Nachrichten liegen im " "Erhaltungsfenster der letzten {turns} Runden." -#: src/iac_code/acp/slash_registry.py:78 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "Komprimierung fehlgeschlagen. Details finden Sie in den Protokollen." -#: src/iac_code/acp/slash_registry.py:83 +#: src/iac_code/acp/slash_registry.py:85 #, python-brace-format msgid "" "Context compacted: {original} → {compacted} tokens ({percent} reduction)." @@ -77,37 +77,41 @@ msgstr "" "Kontext komprimiert: {original} → {compacted} Tokens ({percent} " "Reduzierung). Kontextnutzung: {usage}" -#: src/iac_code/acp/slash_registry.py:97 +#: src/iac_code/acp/slash_registry.py:99 #, python-brace-format msgid "Clear failed: {error}" msgstr "Löschen fehlgeschlagen: {error}" -#: src/iac_code/acp/slash_registry.py:98 +#: src/iac_code/acp/slash_registry.py:100 msgid "Conversation history cleared." msgstr "Konversationsverlauf gelöscht." -#: src/iac_code/acp/slash_registry.py:114 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "Debug-Protokollierung ist aktiv. Protokolldatei: {path}" -#: src/iac_code/acp/slash_registry.py:115 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "Debug-Protokollierung ist inaktiv." -#: src/iac_code/acp/slash_registry.py:119 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "Debug-Protokollierung aktiviert. Protokolldatei: {path}" -#: src/iac_code/acp/slash_registry.py:123 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "Debug-Protokollierung deaktiviert." -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "Verwendung: /debug [on|off]" +#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +msgid "Memory manager is unavailable." +msgstr "Der Speicher-Manager ist nicht verfügbar." + #: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 #: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 msgid "Permission denied." @@ -610,47 +614,55 @@ msgstr "Verzeichnis für persistierte A2A-Routen" msgid "Save the provided routes as a route snapshot" msgstr "Speichert die angegebenen Routen als Routen-Snapshot" -#: src/iac_code/commands/__init__.py:22 +#: src/iac_code/commands/__init__.py:23 msgid "Show available commands" msgstr "Verfügbare Befehle anzeigen" -#: src/iac_code/commands/__init__.py:31 +#: src/iac_code/commands/__init__.py:32 msgid "Clear conversation history" msgstr "Konversationsverlauf löschen" -#: src/iac_code/commands/__init__.py:39 +#: src/iac_code/commands/__init__.py:40 msgid "Show or switch model" msgstr "Modell anzeigen oder wechseln" -#: src/iac_code/commands/__init__.py:48 +#: src/iac_code/commands/__init__.py:49 msgid "Show or switch thinking effort" msgstr "Thinking-Effort anzeigen oder wechseln" -#: src/iac_code/commands/__init__.py:57 +#: src/iac_code/commands/__init__.py:58 msgid "Compact conversation context" msgstr "Konversationskontext komprimieren" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compacting conversation" msgstr "Konversation wird komprimiert" -#: src/iac_code/commands/__init__.py:66 +#: src/iac_code/commands/__init__.py:67 msgid "Exit the application" msgstr "Anwendung beenden" -#: src/iac_code/commands/__init__.py:75 +#: src/iac_code/commands/__init__.py:76 msgid "Authenticate with LLM provider" msgstr "Beim LLM-Anbieter authentifizieren" -#: src/iac_code/commands/__init__.py:84 +#: src/iac_code/commands/__init__.py:85 msgid "Toggle debug logging" msgstr "Debug-Protokollierung umschalten" -#: src/iac_code/commands/__init__.py:93 +#: src/iac_code/commands/__init__.py:94 +msgid "View and manage persistent memories" +msgstr "Persistente Erinnerungen anzeigen und verwalten" + +#: src/iac_code/commands/__init__.py:96 +msgid "[|search |delete |help]" +msgstr "[|search |delete |help]" + +#: src/iac_code/commands/__init__.py:103 msgid "Resume a previous session" msgstr "Eine frühere Sitzung fortsetzen" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:105 msgid "[conversation id or search term]" msgstr "[Konversations-ID oder Suchbegriff]" @@ -969,6 +981,36 @@ msgstr "Befehlsvorschläge anzeigen" msgid "Exit" msgstr "Beenden" +#: src/iac_code/commands/memory.py:10 +msgid "Usage: /memory [|search |delete |help]" +msgstr "Verwendung: /memory [|search |delete |help]" + +#: src/iac_code/commands/memory.py:40 +msgid "Saved memories:" +msgstr "Gespeicherte Erinnerungen:" + +#: src/iac_code/commands/memory.py:40 +msgid "No memories saved yet." +msgstr "Noch keine Erinnerungen gespeichert." + +#: src/iac_code/commands/memory.py:51 +msgid "Matching memories:" +msgstr "Passende Erinnerungen:" + +#: src/iac_code/commands/memory.py:51 +msgid "No matching memories." +msgstr "Keine passenden Erinnerungen." + +#: src/iac_code/commands/memory.py:60 src/iac_code/commands/memory.py:75 +#, python-brace-format +msgid "Memory '{name}' not found." +msgstr "Erinnerung '{name}' nicht gefunden." + +#: src/iac_code/commands/memory.py:64 +#, python-brace-format +msgid "Memory '{name}' deleted." +msgstr "Erinnerung '{name}' gelöscht." + #: src/iac_code/commands/model.py:57 #, python-brace-format msgid "" @@ -2233,6 +2275,22 @@ msgstr "vor {n} Stunde{s}" msgid "{n} day{s} ago" msgstr "vor {n} Tag{s}" +#: src/iac_code/ui/suggestions/command_provider.py:79 +msgid "Search saved memories" +msgstr "Gespeicherte Erinnerungen durchsuchen" + +#: src/iac_code/ui/suggestions/command_provider.py:80 +msgid "Delete a saved memory" +msgstr "Eine gespeicherte Erinnerung löschen" + +#: src/iac_code/ui/suggestions/command_provider.py:81 +msgid "Show memory command help" +msgstr "Hilfe zum memory-Befehl anzeigen" + +#: src/iac_code/ui/suggestions/command_provider.py:116 +msgid "Saved memory" +msgstr "Gespeicherte Erinnerung" + #: src/iac_code/utils/platform.py:39 msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code unter Windows erfordert Git for Windows." 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 8483d4a..4a6c445 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.3.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-01 17:56+0800\n" +"POT-Creation-Date: 2026-06-02 11:24+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: es\n" @@ -41,21 +41,21 @@ msgstr "" "El comando '/{cmd_name}' no está admitido en ACP. Comandos admitidos: " "{supported}" -#: src/iac_code/acp/slash_registry.py:56 +#: src/iac_code/acp/slash_registry.py:58 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "El controlador del comando '/{cmd_name}' no está implementado." -#: src/iac_code/acp/slash_registry.py:68 +#: src/iac_code/acp/slash_registry.py:70 #, python-brace-format msgid "Compaction failed: {error}" msgstr "Error al compactar: {error}" -#: src/iac_code/acp/slash_registry.py:71 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "Nada que compactar: la conversación está vacía." -#: src/iac_code/acp/slash_registry.py:74 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format msgid "" "Conversation too short to compact: all messages are within the recent " @@ -64,14 +64,14 @@ msgstr "" "Conversación demasiado corta para compactar: todos los mensajes están " "dentro de la ventana de retención de las últimas {turns} interacciones." -#: src/iac_code/acp/slash_registry.py:78 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "" "Compaction failed. See logs for details.Compaction failed. See logs for " "details.La compactación falló. Consulte los registros para obtener más " "información." -#: src/iac_code/acp/slash_registry.py:83 +#: src/iac_code/acp/slash_registry.py:85 #, python-brace-format msgid "" "Context compacted: {original} → {compacted} tokens ({percent} reduction)." @@ -80,37 +80,41 @@ msgstr "" "Contexto compactado: {original} → {compacted} tokens (reducción del " "{percent}). Uso del contexto: {usage}" -#: src/iac_code/acp/slash_registry.py:97 +#: src/iac_code/acp/slash_registry.py:99 #, python-brace-format msgid "Clear failed: {error}" msgstr "Error al borrar: {error}" -#: src/iac_code/acp/slash_registry.py:98 +#: src/iac_code/acp/slash_registry.py:100 msgid "Conversation history cleared." msgstr "Historial de conversación borrado." -#: src/iac_code/acp/slash_registry.py:114 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "El registro de depuración está activado. Archivo de registro: {path}" -#: src/iac_code/acp/slash_registry.py:115 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "El registro de depuración está desactivado." -#: src/iac_code/acp/slash_registry.py:119 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "Registro de depuración habilitado. Archivo de registro: {path}" -#: src/iac_code/acp/slash_registry.py:123 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "Registro de depuración deshabilitado." -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "Uso: /debug [on|off]" +#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +msgid "Memory manager is unavailable." +msgstr "El gestor de memoria no está disponible." + #: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 #: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 msgid "Permission denied." @@ -609,47 +613,55 @@ msgstr "Directorio para rutas A2A persistentes" msgid "Save the provided routes as a route snapshot" msgstr "Guarda las rutas proporcionadas como una instantánea de rutas" -#: src/iac_code/commands/__init__.py:22 +#: src/iac_code/commands/__init__.py:23 msgid "Show available commands" msgstr "Mostrar los comandos disponibles" -#: src/iac_code/commands/__init__.py:31 +#: src/iac_code/commands/__init__.py:32 msgid "Clear conversation history" msgstr "Borrar el historial de conversación" -#: src/iac_code/commands/__init__.py:39 +#: src/iac_code/commands/__init__.py:40 msgid "Show or switch model" msgstr "Mostrar o cambiar de modelo" -#: src/iac_code/commands/__init__.py:48 +#: src/iac_code/commands/__init__.py:49 msgid "Show or switch thinking effort" msgstr "Mostrar o cambiar el nivel de razonamiento (effort)" -#: src/iac_code/commands/__init__.py:57 +#: src/iac_code/commands/__init__.py:58 msgid "Compact conversation context" msgstr "Compactar el contexto de la conversación" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compacting conversation" msgstr "Compactando la conversación" -#: src/iac_code/commands/__init__.py:66 +#: src/iac_code/commands/__init__.py:67 msgid "Exit the application" msgstr "Salir de la aplicación" -#: src/iac_code/commands/__init__.py:75 +#: src/iac_code/commands/__init__.py:76 msgid "Authenticate with LLM provider" msgstr "Autenticar con el proveedor LLM" -#: src/iac_code/commands/__init__.py:84 +#: src/iac_code/commands/__init__.py:85 msgid "Toggle debug logging" msgstr "Activar o desactivar el registro de depuración" -#: src/iac_code/commands/__init__.py:93 +#: src/iac_code/commands/__init__.py:94 +msgid "View and manage persistent memories" +msgstr "Ver y administrar memorias persistentes" + +#: src/iac_code/commands/__init__.py:96 +msgid "[|search |delete |help]" +msgstr "[|search |delete |help]" + +#: src/iac_code/commands/__init__.py:103 msgid "Resume a previous session" msgstr "Reanudar una sesión anterior" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:105 msgid "[conversation id or search term]" msgstr "[id de conversación o término de búsqueda]" @@ -968,6 +980,36 @@ msgstr "Mostrar sugerencias de comandos" msgid "Exit" msgstr "Salir" +#: src/iac_code/commands/memory.py:10 +msgid "Usage: /memory [|search |delete |help]" +msgstr "Uso: /memory [|search |delete |help]" + +#: src/iac_code/commands/memory.py:40 +msgid "Saved memories:" +msgstr "Memorias guardadas:" + +#: src/iac_code/commands/memory.py:40 +msgid "No memories saved yet." +msgstr "Todavía no hay memorias guardadas." + +#: src/iac_code/commands/memory.py:51 +msgid "Matching memories:" +msgstr "Memorias coincidentes:" + +#: src/iac_code/commands/memory.py:51 +msgid "No matching memories." +msgstr "No hay memorias coincidentes." + +#: src/iac_code/commands/memory.py:60 src/iac_code/commands/memory.py:75 +#, python-brace-format +msgid "Memory '{name}' not found." +msgstr "No se encontró la memoria '{name}'." + +#: src/iac_code/commands/memory.py:64 +#, python-brace-format +msgid "Memory '{name}' deleted." +msgstr "Memoria '{name}' eliminada." + #: src/iac_code/commands/model.py:57 #, python-brace-format msgid "" @@ -2241,6 +2283,22 @@ msgstr "hace {n} hora{s}" msgid "{n} day{s} ago" msgstr "hace {n} día{s}" +#: src/iac_code/ui/suggestions/command_provider.py:79 +msgid "Search saved memories" +msgstr "Buscar memorias guardadas" + +#: src/iac_code/ui/suggestions/command_provider.py:80 +msgid "Delete a saved memory" +msgstr "Eliminar una memoria guardada" + +#: src/iac_code/ui/suggestions/command_provider.py:81 +msgid "Show memory command help" +msgstr "Mostrar la ayuda del comando memory" + +#: src/iac_code/ui/suggestions/command_provider.py:116 +msgid "Saved memory" +msgstr "Memoria guardada" + #: src/iac_code/utils/platform.py:39 msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code en Windows requiere Git for Windows." 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 cb37d6e..c244520 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.3.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-01 17:56+0800\n" +"POT-Creation-Date: 2026-06-02 11:24+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: fr\n" @@ -41,21 +41,21 @@ msgstr "" "La commande « /{cmd_name} » n’est pas prise en charge via ACP. Commandes " "prises en charge : {supported}" -#: src/iac_code/acp/slash_registry.py:56 +#: src/iac_code/acp/slash_registry.py:58 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "Gestionnaire de la commande « /{cmd_name} » non implémenté." -#: src/iac_code/acp/slash_registry.py:68 +#: src/iac_code/acp/slash_registry.py:70 #, python-brace-format msgid "Compaction failed: {error}" msgstr "Échec de la compaction : {error}" -#: src/iac_code/acp/slash_registry.py:71 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "Rien à compacter : la conversation est vide." -#: src/iac_code/acp/slash_registry.py:74 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format msgid "" "Conversation too short to compact: all messages are within the recent " @@ -64,11 +64,11 @@ msgstr "" "Conversation trop courte pour être compactée : tous les messages se " "situent dans la fenêtre de conservation des {turns} derniers tours." -#: src/iac_code/acp/slash_registry.py:78 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "Échec de la compaction. Consultez les journaux pour plus de détails." -#: src/iac_code/acp/slash_registry.py:83 +#: src/iac_code/acp/slash_registry.py:85 #, python-brace-format msgid "" "Context compacted: {original} → {compacted} tokens ({percent} reduction)." @@ -77,37 +77,41 @@ msgstr "" "Contexte compacté : {original} → {compacted} tokens (réduction " "{percent}). Utilisation du contexte : {usage}" -#: src/iac_code/acp/slash_registry.py:97 +#: src/iac_code/acp/slash_registry.py:99 #, python-brace-format msgid "Clear failed: {error}" msgstr "Échec de l’effacement : {error}" -#: src/iac_code/acp/slash_registry.py:98 +#: src/iac_code/acp/slash_registry.py:100 msgid "Conversation history cleared." msgstr "Historique de conversation effacé." -#: src/iac_code/acp/slash_registry.py:114 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "Journalisation debug activée. Fichier de journal : {path}" -#: src/iac_code/acp/slash_registry.py:115 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "Journalisation debug désactivée." -#: src/iac_code/acp/slash_registry.py:119 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "Journalisation debug activée. Fichier de journal : {path}" -#: src/iac_code/acp/slash_registry.py:123 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "Journalisation debug désactivée." -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "Utilisation : /debug [on|off]" +#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +msgid "Memory manager is unavailable." +msgstr "Le gestionnaire de mémoire est indisponible." + #: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 #: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 msgid "Permission denied." @@ -607,47 +611,55 @@ msgstr "Répertoire pour les routes A2A persistées" msgid "Save the provided routes as a route snapshot" msgstr "Enregistre les routes fournies sous forme d'instantané de routes" -#: src/iac_code/commands/__init__.py:22 +#: src/iac_code/commands/__init__.py:23 msgid "Show available commands" msgstr "Afficher les commandes disponibles" -#: src/iac_code/commands/__init__.py:31 +#: src/iac_code/commands/__init__.py:32 msgid "Clear conversation history" msgstr "Effacer l’historique de conversation" -#: src/iac_code/commands/__init__.py:39 +#: src/iac_code/commands/__init__.py:40 msgid "Show or switch model" msgstr "Afficher ou changer de modèle" -#: src/iac_code/commands/__init__.py:48 +#: src/iac_code/commands/__init__.py:49 msgid "Show or switch thinking effort" msgstr "Afficher ou modifier l’effort de raisonnement" -#: src/iac_code/commands/__init__.py:57 +#: src/iac_code/commands/__init__.py:58 msgid "Compact conversation context" msgstr "Compacter le contexte de conversation" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compacting conversation" msgstr "Compactage de la conversation" -#: src/iac_code/commands/__init__.py:66 +#: src/iac_code/commands/__init__.py:67 msgid "Exit the application" msgstr "Quitter l’application" -#: src/iac_code/commands/__init__.py:75 +#: src/iac_code/commands/__init__.py:76 msgid "Authenticate with LLM provider" msgstr "S’authentifier auprès du fournisseur LLM" -#: src/iac_code/commands/__init__.py:84 +#: src/iac_code/commands/__init__.py:85 msgid "Toggle debug logging" msgstr "Activer ou désactiver la journalisation debug" -#: src/iac_code/commands/__init__.py:93 +#: src/iac_code/commands/__init__.py:94 +msgid "View and manage persistent memories" +msgstr "Afficher et gérer les mémoires persistantes" + +#: src/iac_code/commands/__init__.py:96 +msgid "[|search |delete |help]" +msgstr "[|search |delete |help]" + +#: src/iac_code/commands/__init__.py:103 msgid "Resume a previous session" msgstr "Reprendre une session précédente" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:105 msgid "[conversation id or search term]" msgstr "[identifiant de conversation ou terme de recherche]" @@ -972,6 +984,36 @@ msgstr "Afficher les suggestions de commandes" msgid "Exit" msgstr "Quitter" +#: src/iac_code/commands/memory.py:10 +msgid "Usage: /memory [|search |delete |help]" +msgstr "Utilisation : /memory [|search |delete |help]" + +#: src/iac_code/commands/memory.py:40 +msgid "Saved memories:" +msgstr "Mémoires enregistrées :" + +#: src/iac_code/commands/memory.py:40 +msgid "No memories saved yet." +msgstr "Aucune mémoire enregistrée pour le moment." + +#: src/iac_code/commands/memory.py:51 +msgid "Matching memories:" +msgstr "Mémoires correspondantes :" + +#: src/iac_code/commands/memory.py:51 +msgid "No matching memories." +msgstr "Aucune mémoire correspondante." + +#: src/iac_code/commands/memory.py:60 src/iac_code/commands/memory.py:75 +#, python-brace-format +msgid "Memory '{name}' not found." +msgstr "Mémoire '{name}' introuvable." + +#: src/iac_code/commands/memory.py:64 +#, python-brace-format +msgid "Memory '{name}' deleted." +msgstr "Mémoire '{name}' supprimée." + #: src/iac_code/commands/model.py:57 #, python-brace-format msgid "" @@ -2242,6 +2284,22 @@ msgstr "il y a {n} heure{s}" msgid "{n} day{s} ago" msgstr "il y a {n} jour{s}" +#: src/iac_code/ui/suggestions/command_provider.py:79 +msgid "Search saved memories" +msgstr "Rechercher dans les mémoires enregistrées" + +#: src/iac_code/ui/suggestions/command_provider.py:80 +msgid "Delete a saved memory" +msgstr "Supprimer une mémoire enregistrée" + +#: src/iac_code/ui/suggestions/command_provider.py:81 +msgid "Show memory command help" +msgstr "Afficher l'aide de la commande memory" + +#: src/iac_code/ui/suggestions/command_provider.py:116 +msgid "Saved memory" +msgstr "Mémoire enregistrée" + #: src/iac_code/utils/platform.py:39 msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code sous Windows nécessite Git for Windows." 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 fab60c6..9f7c8d7 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.3.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-01 17:56+0800\n" +"POT-Creation-Date: 2026-06-02 11:24+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: ja\n" @@ -37,32 +37,32 @@ msgid "" "{supported}" msgstr "コマンド '/{cmd_name}' は ACP 上ではサポートされていません。サポートされているコマンド:{supported}" -#: src/iac_code/acp/slash_registry.py:56 +#: src/iac_code/acp/slash_registry.py:58 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "コマンド '/{cmd_name}' のハンドラーは未実装です。" -#: src/iac_code/acp/slash_registry.py:68 +#: src/iac_code/acp/slash_registry.py:70 #, python-brace-format msgid "Compaction failed: {error}" msgstr "圧縮に失敗しました:{error}" -#: src/iac_code/acp/slash_registry.py:71 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "圧縮できる内容がありません:会話が空です。" -#: src/iac_code/acp/slash_registry.py:74 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format msgid "" "Conversation too short to compact: all messages are within the recent " "{turns}-turn preservation window." msgstr "会話が短すぎて圧縮できません:すべてのメッセージが直近 {turns} ターンの保持ウィンドウ内にあります。" -#: src/iac_code/acp/slash_registry.py:78 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "圧縮に失敗しました。詳細はログをご確認ください。" -#: src/iac_code/acp/slash_registry.py:83 +#: src/iac_code/acp/slash_registry.py:85 #, python-brace-format msgid "" "Context compacted: {original} → {compacted} tokens ({percent} reduction)." @@ -71,37 +71,41 @@ msgstr "" "コンテキストを圧縮しました:{original} → {compacted} tokens({percent} 削減)。 " "コンテキスト使用量:{usage}" -#: src/iac_code/acp/slash_registry.py:97 +#: src/iac_code/acp/slash_registry.py:99 #, python-brace-format msgid "Clear failed: {error}" msgstr "クリアに失敗しました:{error}" -#: src/iac_code/acp/slash_registry.py:98 +#: src/iac_code/acp/slash_registry.py:100 msgid "Conversation history cleared." msgstr "会話履歴をクリアしました。" -#: src/iac_code/acp/slash_registry.py:114 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "デバッグログはオンです。ログファイル:{path}" -#: src/iac_code/acp/slash_registry.py:115 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "デバッグログはオフです。" -#: src/iac_code/acp/slash_registry.py:119 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "デバッグログを有効にしました。ログファイル:{path}" -#: src/iac_code/acp/slash_registry.py:123 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "デバッグログを無効にしました。" -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "使用方法:/debug [on|off]" +#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +msgid "Memory manager is unavailable." +msgstr "メモリマネージャーを利用できません。" + #: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 #: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 msgid "Permission denied." @@ -583,47 +587,55 @@ msgstr "永続化された A2A ルート用のディレクトリ" msgid "Save the provided routes as a route snapshot" msgstr "指定されたルートをルートスナップショットとして保存します" -#: src/iac_code/commands/__init__.py:22 +#: src/iac_code/commands/__init__.py:23 msgid "Show available commands" msgstr "利用可能なコマンドを表示します" -#: src/iac_code/commands/__init__.py:31 +#: src/iac_code/commands/__init__.py:32 msgid "Clear conversation history" msgstr "会話履歴をクリアします" -#: src/iac_code/commands/__init__.py:39 +#: src/iac_code/commands/__init__.py:40 msgid "Show or switch model" msgstr "モデルを表示または切り替えます" -#: src/iac_code/commands/__init__.py:48 +#: src/iac_code/commands/__init__.py:49 msgid "Show or switch thinking effort" msgstr "思考の負荷(effort)を表示または切り替えます" -#: src/iac_code/commands/__init__.py:57 +#: src/iac_code/commands/__init__.py:58 msgid "Compact conversation context" msgstr "会話コンテキストを圧縮します" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compacting conversation" msgstr "会話を圧縮しています" -#: src/iac_code/commands/__init__.py:66 +#: src/iac_code/commands/__init__.py:67 msgid "Exit the application" msgstr "アプリケーションを終了します" -#: src/iac_code/commands/__init__.py:75 +#: src/iac_code/commands/__init__.py:76 msgid "Authenticate with LLM provider" msgstr "LLM プロバイダーで認証を行います" -#: src/iac_code/commands/__init__.py:84 +#: src/iac_code/commands/__init__.py:85 msgid "Toggle debug logging" msgstr "デバッグログのオン/オフを切り替えます" -#: src/iac_code/commands/__init__.py:93 +#: src/iac_code/commands/__init__.py:94 +msgid "View and manage persistent memories" +msgstr "永続メモリを表示および管理" + +#: src/iac_code/commands/__init__.py:96 +msgid "[|search |delete |help]" +msgstr "[<名前>|search <検索語>|delete <名前>|help]" + +#: src/iac_code/commands/__init__.py:103 msgid "Resume a previous session" msgstr "以前のセッションを再開します" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:105 msgid "[conversation id or search term]" msgstr "[会話 ID または検索語]" @@ -944,6 +956,36 @@ msgstr "コマンド候補を表示" msgid "Exit" msgstr "終了" +#: src/iac_code/commands/memory.py:10 +msgid "Usage: /memory [|search |delete |help]" +msgstr "使用法: /memory [<名前>|search <検索語>|delete <名前>|help]" + +#: src/iac_code/commands/memory.py:40 +msgid "Saved memories:" +msgstr "保存済みメモリ:" + +#: src/iac_code/commands/memory.py:40 +msgid "No memories saved yet." +msgstr "保存済みメモリはまだありません。" + +#: src/iac_code/commands/memory.py:51 +msgid "Matching memories:" +msgstr "一致するメモリ:" + +#: src/iac_code/commands/memory.py:51 +msgid "No matching memories." +msgstr "一致するメモリはありません。" + +#: src/iac_code/commands/memory.py:60 src/iac_code/commands/memory.py:75 +#, python-brace-format +msgid "Memory '{name}' not found." +msgstr "メモリ '{name}' が見つかりません。" + +#: src/iac_code/commands/memory.py:64 +#, python-brace-format +msgid "Memory '{name}' deleted." +msgstr "メモリ '{name}' を削除しました。" + #: src/iac_code/commands/model.py:57 #, python-brace-format msgid "" @@ -2179,6 +2221,22 @@ msgstr "{n} 時間{s}前" msgid "{n} day{s} ago" msgstr "{n} 日{s}前" +#: src/iac_code/ui/suggestions/command_provider.py:79 +msgid "Search saved memories" +msgstr "保存済みメモリを検索" + +#: src/iac_code/ui/suggestions/command_provider.py:80 +msgid "Delete a saved memory" +msgstr "保存済みメモリを削除" + +#: src/iac_code/ui/suggestions/command_provider.py:81 +msgid "Show memory command help" +msgstr "memory コマンドのヘルプを表示" + +#: src/iac_code/ui/suggestions/command_provider.py:116 +msgid "Saved memory" +msgstr "保存済みメモリ" + #: src/iac_code/utils/platform.py:39 msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code を Windows で使用するには Git for Windows が必要です。" 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 a1fbd13..7008b3b 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.3.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-01 17:56+0800\n" +"POT-Creation-Date: 2026-06-02 11:24+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: pt\n" @@ -41,21 +41,21 @@ msgstr "" "O comando '/{cmd_name}' não é compatível com ACP. Comandos compatíveis: " "{supported}" -#: src/iac_code/acp/slash_registry.py:56 +#: src/iac_code/acp/slash_registry.py:58 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "Tratador do comando '/{cmd_name}' não implementado." -#: src/iac_code/acp/slash_registry.py:68 +#: src/iac_code/acp/slash_registry.py:70 #, python-brace-format msgid "Compaction failed: {error}" msgstr "Falha ao compactar: {error}" -#: src/iac_code/acp/slash_registry.py:71 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "Nada para compactar: a conversa está vazia." -#: src/iac_code/acp/slash_registry.py:74 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format msgid "" "Conversation too short to compact: all messages are within the recent " @@ -64,11 +64,11 @@ msgstr "" "Conversa curta demais para compactar: todas as mensagens estão na janela " "de preservação das últimas {turns} interações." -#: src/iac_code/acp/slash_registry.py:78 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "Falha na compactação. Consulte os logs para detalhes." -#: src/iac_code/acp/slash_registry.py:83 +#: src/iac_code/acp/slash_registry.py:85 #, python-brace-format msgid "" "Context compacted: {original} → {compacted} tokens ({percent} reduction)." @@ -77,37 +77,41 @@ msgstr "" "Contexto compactado: {original} → {compacted} tokens (redução de " "{percent}). Uso do contexto: {usage}" -#: src/iac_code/acp/slash_registry.py:97 +#: src/iac_code/acp/slash_registry.py:99 #, python-brace-format msgid "Clear failed: {error}" msgstr "Falha ao limpar: {error}" -#: src/iac_code/acp/slash_registry.py:98 +#: src/iac_code/acp/slash_registry.py:100 msgid "Conversation history cleared." msgstr "Histórico da conversa limpo." -#: src/iac_code/acp/slash_registry.py:114 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "Debug ativado. Arquivo de log: {path}" -#: src/iac_code/acp/slash_registry.py:115 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "Debug desativado." -#: src/iac_code/acp/slash_registry.py:119 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "Debug ativado. Arquivo de log: {path}" -#: src/iac_code/acp/slash_registry.py:123 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "Debug desativado." -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "Uso: /debug [on|off]" +#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +msgid "Memory manager is unavailable." +msgstr "O gerenciador de memória está indisponível." + #: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 #: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 msgid "Permission denied." @@ -603,47 +607,55 @@ msgstr "Diretório para rotas A2A persistidas" msgid "Save the provided routes as a route snapshot" msgstr "Salva as rotas fornecidas como um snapshot de rotas" -#: src/iac_code/commands/__init__.py:22 +#: src/iac_code/commands/__init__.py:23 msgid "Show available commands" msgstr "Mostrar comandos disponíveis" -#: src/iac_code/commands/__init__.py:31 +#: src/iac_code/commands/__init__.py:32 msgid "Clear conversation history" msgstr "Limpar histórico da conversa" -#: src/iac_code/commands/__init__.py:39 +#: src/iac_code/commands/__init__.py:40 msgid "Show or switch model" msgstr "Exibir ou trocar modelo" -#: src/iac_code/commands/__init__.py:48 +#: src/iac_code/commands/__init__.py:49 msgid "Show or switch thinking effort" msgstr "Exibir ou alterar o nível de esforço de raciocínio" -#: src/iac_code/commands/__init__.py:57 +#: src/iac_code/commands/__init__.py:58 msgid "Compact conversation context" msgstr "Compactar contexto da conversa" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compacting conversation" msgstr "Compactando conversa" -#: src/iac_code/commands/__init__.py:66 +#: src/iac_code/commands/__init__.py:67 msgid "Exit the application" msgstr "Encerrar o aplicativo" -#: src/iac_code/commands/__init__.py:75 +#: src/iac_code/commands/__init__.py:76 msgid "Authenticate with LLM provider" msgstr "Autenticar com o provedor LLM" -#: src/iac_code/commands/__init__.py:84 +#: src/iac_code/commands/__init__.py:85 msgid "Toggle debug logging" msgstr "Alternar debug" -#: src/iac_code/commands/__init__.py:93 +#: src/iac_code/commands/__init__.py:94 +msgid "View and manage persistent memories" +msgstr "Ver e gerenciar memórias persistentes" + +#: src/iac_code/commands/__init__.py:96 +msgid "[|search |delete |help]" +msgstr "[|search |delete |help]" + +#: src/iac_code/commands/__init__.py:103 msgid "Resume a previous session" msgstr "Retomar uma sessão anterior" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:105 msgid "[conversation id or search term]" msgstr "[ID da conversa ou termo de busca]" @@ -962,6 +974,36 @@ msgstr "Mostrar sugestões de comando" msgid "Exit" msgstr "Sair" +#: src/iac_code/commands/memory.py:10 +msgid "Usage: /memory [|search |delete |help]" +msgstr "Uso: /memory [|search |delete |help]" + +#: src/iac_code/commands/memory.py:40 +msgid "Saved memories:" +msgstr "Memórias salvas:" + +#: src/iac_code/commands/memory.py:40 +msgid "No memories saved yet." +msgstr "Ainda não há memórias salvas." + +#: src/iac_code/commands/memory.py:51 +msgid "Matching memories:" +msgstr "Memórias correspondentes:" + +#: src/iac_code/commands/memory.py:51 +msgid "No matching memories." +msgstr "Nenhuma memória correspondente." + +#: src/iac_code/commands/memory.py:60 src/iac_code/commands/memory.py:75 +#, python-brace-format +msgid "Memory '{name}' not found." +msgstr "Memória '{name}' não encontrada." + +#: src/iac_code/commands/memory.py:64 +#, python-brace-format +msgid "Memory '{name}' deleted." +msgstr "Memória '{name}' excluída." + #: src/iac_code/commands/model.py:57 #, python-brace-format msgid "" @@ -2221,6 +2263,22 @@ msgstr "há {n} hora{s}" msgid "{n} day{s} ago" msgstr "há {n} dia{s}" +#: src/iac_code/ui/suggestions/command_provider.py:79 +msgid "Search saved memories" +msgstr "Pesquisar memórias salvas" + +#: src/iac_code/ui/suggestions/command_provider.py:80 +msgid "Delete a saved memory" +msgstr "Excluir uma memória salva" + +#: src/iac_code/ui/suggestions/command_provider.py:81 +msgid "Show memory command help" +msgstr "Mostrar ajuda do comando memory" + +#: src/iac_code/ui/suggestions/command_provider.py:116 +msgid "Saved memory" +msgstr "Memória salva" + #: src/iac_code/utils/platform.py:39 msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code no Windows requer o Git for Windows." 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 ea8f6f6..a178f94 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.3.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-01 17:56+0800\n" +"POT-Creation-Date: 2026-06-02 11:24+0800\n" "PO-Revision-Date: 2026-04-02 00:00+0000\n" "Last-Translator: \n" "Language: zh\n" @@ -35,69 +35,73 @@ msgid "" "{supported}" msgstr "命令 '/{cmd_name}' 不支持通过 ACP 使用。支持的命令:{supported}" -#: src/iac_code/acp/slash_registry.py:56 +#: src/iac_code/acp/slash_registry.py:58 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "命令 '/{cmd_name}' 的处理器尚未实现。" -#: src/iac_code/acp/slash_registry.py:68 +#: src/iac_code/acp/slash_registry.py:70 #, python-brace-format msgid "Compaction failed: {error}" msgstr "压缩失败:{error}" -#: src/iac_code/acp/slash_registry.py:71 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "无内容可压缩:对话为空。" -#: src/iac_code/acp/slash_registry.py:74 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format msgid "" "Conversation too short to compact: all messages are within the recent " "{turns}-turn preservation window." msgstr "对话过短,无需压缩:所有消息都在最近 {turns} 轮保留窗口内。" -#: src/iac_code/acp/slash_registry.py:78 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "压缩失败,详情请查看日志。" -#: src/iac_code/acp/slash_registry.py:83 +#: src/iac_code/acp/slash_registry.py:85 #, python-brace-format msgid "" "Context compacted: {original} → {compacted} tokens ({percent} reduction)." " Context usage: {usage}" msgstr "上下文已压缩:{original} → {compacted} tokens(减少 {percent})。上下文用量:{usage}" -#: src/iac_code/acp/slash_registry.py:97 +#: src/iac_code/acp/slash_registry.py:99 #, python-brace-format msgid "Clear failed: {error}" msgstr "清除失败:{error}" -#: src/iac_code/acp/slash_registry.py:98 +#: src/iac_code/acp/slash_registry.py:100 msgid "Conversation history cleared." msgstr "对话历史已清除。" -#: src/iac_code/acp/slash_registry.py:114 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "调试日志已启用。日志文件:{path}" -#: src/iac_code/acp/slash_registry.py:115 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "调试日志已关闭。" -#: src/iac_code/acp/slash_registry.py:119 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "调试日志已启用。日志文件:{path}" -#: src/iac_code/acp/slash_registry.py:123 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "调试日志已关闭。" -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "用法:/debug [on|off]" +#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +msgid "Memory manager is unavailable." +msgstr "记忆管理器不可用。" + #: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 #: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 msgid "Permission denied." @@ -575,47 +579,55 @@ msgstr "持久化 A2A 路由的目录" msgid "Save the provided routes as a route snapshot" msgstr "将提供的路由保存为路由快照" -#: src/iac_code/commands/__init__.py:22 +#: src/iac_code/commands/__init__.py:23 msgid "Show available commands" msgstr "显示可用命令" -#: src/iac_code/commands/__init__.py:31 +#: src/iac_code/commands/__init__.py:32 msgid "Clear conversation history" msgstr "清除对话历史" -#: src/iac_code/commands/__init__.py:39 +#: src/iac_code/commands/__init__.py:40 msgid "Show or switch model" msgstr "显示或切换模型" -#: src/iac_code/commands/__init__.py:48 +#: src/iac_code/commands/__init__.py:49 msgid "Show or switch thinking effort" msgstr "显示或切换思考强度" -#: src/iac_code/commands/__init__.py:57 +#: src/iac_code/commands/__init__.py:58 msgid "Compact conversation context" msgstr "压缩对话上下文" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compacting conversation" msgstr "正在压缩对话" -#: src/iac_code/commands/__init__.py:66 +#: src/iac_code/commands/__init__.py:67 msgid "Exit the application" msgstr "退出应用程序" -#: src/iac_code/commands/__init__.py:75 +#: src/iac_code/commands/__init__.py:76 msgid "Authenticate with LLM provider" msgstr "配置 LLM 提供商认证" -#: src/iac_code/commands/__init__.py:84 +#: src/iac_code/commands/__init__.py:85 msgid "Toggle debug logging" msgstr "切换调试日志开关" -#: src/iac_code/commands/__init__.py:93 +#: src/iac_code/commands/__init__.py:94 +msgid "View and manage persistent memories" +msgstr "查看和管理持久记忆" + +#: src/iac_code/commands/__init__.py:96 +msgid "[|search |delete |help]" +msgstr "[<名称>|search <查询>|delete <名称>|help]" + +#: src/iac_code/commands/__init__.py:103 msgid "Resume a previous session" msgstr "恢复之前的会话" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:105 msgid "[conversation id or search term]" msgstr "[会话 ID 或搜索词]" @@ -934,6 +946,36 @@ msgstr "显示命令建议" msgid "Exit" msgstr "退出" +#: src/iac_code/commands/memory.py:10 +msgid "Usage: /memory [|search |delete |help]" +msgstr "用法:/memory [<名称>|search <查询>|delete <名称>|help]" + +#: src/iac_code/commands/memory.py:40 +msgid "Saved memories:" +msgstr "已保存的记忆:" + +#: src/iac_code/commands/memory.py:40 +msgid "No memories saved yet." +msgstr "尚未保存任何记忆。" + +#: src/iac_code/commands/memory.py:51 +msgid "Matching memories:" +msgstr "匹配的记忆:" + +#: src/iac_code/commands/memory.py:51 +msgid "No matching memories." +msgstr "没有匹配的记忆。" + +#: src/iac_code/commands/memory.py:60 src/iac_code/commands/memory.py:75 +#, python-brace-format +msgid "Memory '{name}' not found." +msgstr "未找到记忆 '{name}'。" + +#: src/iac_code/commands/memory.py:64 +#, python-brace-format +msgid "Memory '{name}' deleted." +msgstr "记忆 '{name}' 已删除。" + #: src/iac_code/commands/model.py:57 #, python-brace-format msgid "" @@ -2158,6 +2200,22 @@ msgstr "{n} 小时前" msgid "{n} day{s} ago" msgstr "{n} 天前" +#: src/iac_code/ui/suggestions/command_provider.py:79 +msgid "Search saved memories" +msgstr "搜索已保存的记忆" + +#: src/iac_code/ui/suggestions/command_provider.py:80 +msgid "Delete a saved memory" +msgstr "删除已保存的记忆" + +#: src/iac_code/ui/suggestions/command_provider.py:81 +msgid "Show memory command help" +msgstr "显示 memory 命令帮助" + +#: src/iac_code/ui/suggestions/command_provider.py:116 +msgid "Saved memory" +msgstr "已保存的记忆" + #: src/iac_code/utils/platform.py:39 msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code 在 Windows 上需要安装 Git for Windows。" diff --git a/src/iac_code/memory/memory_manager.py b/src/iac_code/memory/memory_manager.py index bfe0369..e626e96 100644 --- a/src/iac_code/memory/memory_manager.py +++ b/src/iac_code/memory/memory_manager.py @@ -18,8 +18,9 @@ class MemoryManager: def __init__(self, memory_dir: str): - self._memory_dir = memory_dir - ensure_private_dir(Path(memory_dir)) + memory_path = ensure_private_dir(Path(memory_dir)) + self._memory_dir = memory_path + self._memory_root = memory_path.resolve() @staticmethod def _validate_name(name: str) -> str: @@ -34,32 +35,39 @@ def _validate_name(name: str) -> str: raise ValueError(f"Invalid memory name: {name!r}") return cleaned - def _memory_path(self, name: str) -> str: + def _memory_path(self, name: str) -> Path: safe_name = self._validate_name(name) - return os.path.join(self._memory_dir, f"{safe_name}.md") + return self._memory_dir / f"{safe_name}.md" - def _index_path(self) -> str: - return os.path.join(self._memory_dir, INDEX_FILE) + def _index_path(self) -> Path: + return self._memory_dir / INDEX_FILE def save(self, name: str, content: str, memory_type: str, description: str) -> None: if memory_type not in MEMORY_TYPES: raise ValueError(f"Invalid memory type: {memory_type}") file_content = f"---\nname: {name}\ndescription: {description}\ntype: {memory_type}\n---\n\n{content}\n" path = self._memory_path(name) + self._ensure_writable_path(path) + self._ensure_writable_path(self._index_path()) with open(path, "w", encoding="utf-8", newline="\n") as f: f.write(file_content) - ensure_private_file(Path(path)) + ensure_private_file(path) self._update_index() def load(self, name: str) -> dict[str, Any] | None: path = self._memory_path(name) - if not os.path.exists(path): + safe_path = self._safe_existing_file(path) + if safe_path is None: return None - return self._load_memory_file(Path(path)) + return self._load_memory_file(safe_path) def delete(self, name: str) -> None: path = self._memory_path(name) - if os.path.exists(path): + self._ensure_writable_path(self._index_path()) + if path.is_symlink(): + raise ValueError(f"Invalid memory path: {path.name}") + if path.exists(): + self._ensure_writable_path(path) os.remove(path) self._update_index() @@ -71,11 +79,26 @@ def list_memories(self) -> list[dict[str, Any]]: memories.append(mem) return memories + def search(self, query: str) -> list[dict[str, Any]]: + needle = query.strip().casefold() + if not needle: + return [] + + matches: list[dict[str, Any]] = [] + for memory in self.list_memories(): + haystack = "\n".join( + str(memory.get(field, "")) for field in ("name", "description", "type", "content") + ).casefold() + if needle in haystack: + matches.append(memory) + return matches + def get_index_content(self) -> str: path = self._index_path() - if not os.path.exists(path): + safe_path = self._safe_existing_file(path) + if safe_path is None: return "" - with open(path, encoding="utf-8") as f: + with open(safe_path, encoding="utf-8") as f: return f.read() def get_prompt_content(self) -> str: @@ -91,18 +114,50 @@ def _update_index(self) -> None: if mem: entries.append(f"- [{path.stem}]({path.name}) — {mem.get('description', '')}") index_path = self._index_path() + self._ensure_writable_path(index_path) with open(index_path, "w", encoding="utf-8", newline="\n") as f: f.write("\n".join(entries[:MAX_INDEX_LINES]) + "\n") - ensure_private_file(Path(index_path)) + ensure_private_file(index_path) def _iter_memory_files(self) -> list[Path]: - root = Path(self._memory_dir) + root = self._memory_dir return [ - path + safe_path for path in root.iterdir() - if path.is_file() and path.suffix == ".md" and path.name.casefold() != INDEX_FILE.casefold() + if (safe_path := self._safe_existing_file(path)) is not None + and path.suffix == ".md" + and path.name.casefold() != INDEX_FILE.casefold() ] + def _safe_existing_file(self, path: Path) -> Path | None: + if path.is_symlink(): + return None + try: + resolved = path.resolve(strict=True) + except (OSError, RuntimeError): + return None + if not resolved.is_relative_to(self._memory_root) or not path.is_file(): + return None + return path + + def _ensure_writable_path(self, path: Path) -> None: + if path.is_symlink(): + raise ValueError(f"Invalid memory path: {path.name}") + try: + parent = path.parent.resolve(strict=True) + except (OSError, RuntimeError) as exc: + raise ValueError(f"Invalid memory path: {path.name}") from exc + if parent != self._memory_root: + raise ValueError(f"Invalid memory path: {path.name}") + if not path.exists(): + return + try: + resolved = path.resolve(strict=True) + except (OSError, RuntimeError) as exc: + raise ValueError(f"Invalid memory path: {path.name}") from exc + if not resolved.is_relative_to(self._memory_root) or not path.is_file(): + raise ValueError(f"Invalid memory path: {path.name}") + def _load_memory_file(self, path: Path) -> dict[str, Any] | None: try: return self._parse_memory_file(path.read_text(encoding="utf-8")) diff --git a/src/iac_code/memory/memory_tools.py b/src/iac_code/memory/memory_tools.py index b8d274a..caba4b6 100644 --- a/src/iac_code/memory/memory_tools.py +++ b/src/iac_code/memory/memory_tools.py @@ -57,7 +57,12 @@ def name(self) -> str: @property def description(self) -> str: - return f"Save a persistent memory. Types: {', '.join(sorted(MEMORY_TYPES))}." + types = ", ".join(sorted(MEMORY_TYPES)) + return ( + "Save a persistent memory. Use when the user explicitly asks you to remember or preserve information. " + "Choose a concise, stable name, an appropriate type, a short description, and the useful content to keep. " + f"Types: {types}." + ) @property def input_schema(self) -> dict[str, Any]: diff --git a/src/iac_code/ui/repl.py b/src/iac_code/ui/repl.py index e1e4f79..751120e 100644 --- a/src/iac_code/ui/repl.py +++ b/src/iac_code/ui/repl.py @@ -248,7 +248,7 @@ def __init__( cwd = os.getcwd() self._suggestion_aggregator = SuggestionAggregator( [ - CommandProvider(self.command_registry), + CommandProvider(self.command_registry, memory_manager=self._memory_manager), SkillProvider(self.command_registry), FileProvider(cwd), DirectoryProvider(cwd), diff --git a/src/iac_code/ui/suggestions/command_provider.py b/src/iac_code/ui/suggestions/command_provider.py index c6a3d04..1aaf084 100644 --- a/src/iac_code/ui/suggestions/command_provider.py +++ b/src/iac_code/ui/suggestions/command_provider.py @@ -2,7 +2,10 @@ from __future__ import annotations +from typing import Any + from iac_code.commands.registry import CommandRegistry, LocalCommand +from iac_code.i18n import _ from iac_code.ui.suggestions.types import CompletionToken, SuggestionItem, SuggestionProvider @@ -11,14 +14,18 @@ class CommandProvider(SuggestionProvider): trigger = "/" - def __init__(self, registry: CommandRegistry) -> None: + def __init__(self, registry: CommandRegistry, memory_manager: Any | None = None) -> None: self._registry = registry + self._memory_manager = memory_manager def provide(self, token: CompletionToken) -> list[SuggestionItem]: """Return suggestions for the given completion token.""" # Strip the leading "/" to get the query query = token.text[1:] if token.text.startswith("/") else token.text + if self._is_memory_argument_query(query): + return self._memory_argument_suggestions(query) + matches = self._registry.fuzzy_search(query) items: list[SuggestionItem] = [] @@ -41,3 +48,88 @@ def provide(self, token: CompletionToken) -> list[SuggestionItem]: ) return items + + @staticmethod + def _is_memory_argument_query(query: str) -> bool: + return query.startswith("memory") and len(query) > len("memory") and query[len("memory")].isspace() + + def _memory_argument_suggestions(self, query: str) -> list[SuggestionItem]: + arg_text = query[len("memory") :].lstrip() + has_trailing_space = bool(arg_text) and arg_text[-1].isspace() + parts = arg_text.split() + + if not parts: + return self._memory_first_argument_suggestions("") + + action = parts[0].lower() + if action == "delete" and (has_trailing_space or len(parts) > 1): + prefix = parts[1] if len(parts) > 1 else "" + return self._memory_name_suggestions(prefix, command_prefix="/memory delete ") + + if action == "search" and has_trailing_space: + return [] + + if len(parts) == 1 and not has_trailing_space: + return self._memory_first_argument_suggestions(parts[0]) + + return [] + + def _memory_first_argument_suggestions(self, prefix: str) -> list[SuggestionItem]: + suggestions = [ + self._memory_action_item("search", _("Search saved memories"), "/memory search ", prefix), + self._memory_action_item("delete", _("Delete a saved memory"), "/memory delete ", prefix), + self._memory_action_item("help", _("Show memory command help"), "/memory help", prefix), + ] + suggestions.extend(self._memory_name_suggestions(prefix, command_prefix="/memory ")) + return [item for item in suggestions if item is not None] + + def _memory_action_item( + self, + name: str, + description: str, + completion: str, + prefix: str, + ) -> SuggestionItem | None: + if not self._matches_prefix(name, prefix): + return None + return SuggestionItem( + id=f"cmd:memory:{name}", + display_text=name, + completion=completion, + description=description, + icon="/", + source="command", + score=1000.0 - len(name), + ) + + def _memory_name_suggestions(self, prefix: str, *, command_prefix: str) -> list[SuggestionItem]: + items: list[SuggestionItem] = [] + for memory in self._memory_entries(): + name = str(memory.get("name", "")) + if not name or not self._matches_prefix(name, prefix): + continue + items.append( + SuggestionItem( + id=f"cmd:memory:{name}", + display_text=name, + completion=f"{command_prefix}{name}", + description=str(memory.get("description") or _("Saved memory")), + icon="/", + source="command", + score=500.0 - len(name), + ) + ) + return sorted(items, key=lambda item: item.display_text) + + def _memory_entries(self) -> list[dict[str, Any]]: + if self._memory_manager is None: + return [] + try: + memories = self._memory_manager.list_memories() + except (OSError, ValueError): + return [] + return [memory for memory in memories if isinstance(memory, dict)] + + @staticmethod + def _matches_prefix(value: str, prefix: str) -> bool: + return value.casefold().startswith(prefix.casefold()) diff --git a/src/iac_code/ui/suggestions/token_extractor.py b/src/iac_code/ui/suggestions/token_extractor.py index 65e6bd6..23d99f8 100644 --- a/src/iac_code/ui/suggestions/token_extractor.py +++ b/src/iac_code/ui/suggestions/token_extractor.py @@ -28,6 +28,10 @@ def extract(self, text: str, cursor_pos: int) -> CompletionToken | None: # Clamp cursor_pos to valid range end = min(cursor_pos, len(text)) + slash_token = self._extract_slash_command(text, end) + if slash_token is not None: + return slash_token + # Walk backwards to find start of token token_start = end while token_start > 0 and _is_token_char(text[token_start - 1]): @@ -86,3 +90,19 @@ def extract(self, text: str, cursor_pos: int) -> CompletionToken | None: return None return None + + @staticmethod + def _extract_slash_command(text: str, end: int) -> CompletionToken | None: + """Return a slash-command token spanning arguments on the current line.""" + line_start = text.rfind("\n", 0, end) + 1 + for index in range(line_start, end): + if text[index] != "/": + continue + if index == 0 or text[index - 1] in (" ", "\t", "\n"): + return CompletionToken( + text=text[index:end], + start=index, + end=end, + trigger="/", + ) + return None diff --git a/tests/acp/test_sessions.py b/tests/acp/test_sessions.py index 83ed1f1..9dbd49d 100644 --- a/tests/acp/test_sessions.py +++ b/tests/acp/test_sessions.py @@ -189,6 +189,24 @@ async def test_acp_session_streams_text_update() -> None: assert conn.updates[0][1].session_update == "agent_message_chunk" +class _SessionMemoryManager: + def list_memories(self): + return [{"name": "user-role", "type": "user", "description": "Role", "content": "Senior engineer"}] + + +@pytest.mark.asyncio +async def test_acp_session_slash_memory_uses_session_memory_manager() -> None: + conn = _RecordingFakeConn() + session = ACPSession("s-memory", _RecordingFakeLoop(), conn, memory_manager=_SessionMemoryManager()) + + response = await session.prompt([acp.schema.TextContentBlock(type="text", text="/memory")]) + + assert response.stop_reason == "end_turn" + assert conn.updates[0][0] == "s-memory" + assert conn.updates[0][1].session_update == "agent_message_chunk" + assert "user-role - Role" in conn.updates[0][1].content.text + + # --------------------------------------------------------------------------- # ContextVar isolation tests (from test_context_var.py) # --------------------------------------------------------------------------- diff --git a/tests/acp/test_slash_registry.py b/tests/acp/test_slash_registry.py index a932e76..4f14bc0 100644 --- a/tests/acp/test_slash_registry.py +++ b/tests/acp/test_slash_registry.py @@ -197,3 +197,80 @@ async def test_debug_off(registry: ACPSlashRegistry) -> None: async def test_debug_invalid_arg(registry: ACPSlashRegistry) -> None: result = await registry.execute("/debug foo", agent_loop=None) assert "usage" in result.lower() or "/debug" in result.lower() + + +# --------------------------------------------------------------------------- +# execute — /memory +# --------------------------------------------------------------------------- + + +class _MemoryManager: + def __init__(self): + self.memories = { + "user-role": {"name": "user-role", "type": "user", "description": "Role", "content": "Senior engineer"}, + "feedback-testing": { + "name": "feedback-testing", + "type": "feedback", + "description": "Testing", + "content": "Prefer integration tests", + }, + } + self.deleted: list[str] = [] + + def list_memories(self): + return list(self.memories.values()) + + def load(self, name): + if name == "../escape": + raise ValueError("Invalid memory name: '../escape'") + return self.memories.get(name) + + def delete(self, name): + self.deleted.append(name) + self.memories.pop(name, None) + + def search(self, query): + query = query.casefold() + return [ + memory + for memory in self.memories.values() + if query + in "\n".join(str(memory.get(field, "")) for field in ("name", "description", "type", "content")).casefold() + ] + + +@pytest.mark.asyncio +async def test_memory_without_manager_returns_unavailable(registry: ACPSlashRegistry) -> None: + result = await registry.execute("/memory", agent_loop=None) + assert result == "Memory manager is unavailable." + + +@pytest.mark.asyncio +async def test_memory_list_view_search_delete(registry: ACPSlashRegistry) -> None: + memory_manager = _MemoryManager() + + listed = await registry.execute("/memory", agent_loop=None, memory_manager=memory_manager) + viewed = await registry.execute("/memory user-role", agent_loop=None, memory_manager=memory_manager) + searched = await registry.execute("/memory search integration", agent_loop=None, memory_manager=memory_manager) + deleted = await registry.execute("/memory delete user-role", agent_loop=None, memory_manager=memory_manager) + + assert "Saved memories:" in listed + assert viewed == "[user] Role\n\nSenior engineer" + assert searched == "Matching memories:\n - feedback-testing - Testing" + assert deleted == "Memory 'user-role' deleted." + assert memory_manager.deleted == ["user-role"] + + +@pytest.mark.asyncio +async def test_memory_help_missing_invalid_name_and_unknown_usage(registry: ACPSlashRegistry) -> None: + memory_manager = _MemoryManager() + + helped = await registry.execute("/memory help", agent_loop=None, memory_manager=memory_manager) + missing = await registry.execute("/memory missing", agent_loop=None, memory_manager=memory_manager) + invalid = await registry.execute("/memory ../escape", agent_loop=None, memory_manager=memory_manager) + unknown = await registry.execute("/memory remove user-role", agent_loop=None, memory_manager=memory_manager) + + assert helped == "Usage: /memory [|search |delete |help]" + assert missing == "Memory 'missing' not found." + assert invalid == "Invalid memory name: '../escape'" + assert unknown == "Usage: /memory [|search |delete |help]" diff --git a/tests/commands/test_memory.py b/tests/commands/test_memory.py new file mode 100644 index 0000000..00b3649 --- /dev/null +++ b/tests/commands/test_memory.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import pytest + +from iac_code.commands.memory import execute_memory_command, memory_command +from iac_code.memory.memory_manager import MemoryManager + + +@pytest.fixture +def manager(tmp_path): + mgr = MemoryManager(memory_dir=str(tmp_path)) + mgr.save("user-role", "Senior cloud engineer", memory_type="user", description="Role") + mgr.save("feedback-testing", "Prefer integration tests", memory_type="feedback", description="Testing") + return mgr + + +class _Context: + def __init__(self, manager): + self.repl = type("Repl", (), {"_memory_manager": manager})() + + +def test_execute_memory_command_lists_memories(manager): + output = execute_memory_command(manager, []) + assert "Saved memories:" in output + assert "feedback-testing - Testing" in output + assert "user-role - Role" in output + + +def test_execute_memory_command_lists_empty(tmp_path): + output = execute_memory_command(MemoryManager(memory_dir=str(tmp_path)), []) + assert output == "No memories saved yet." + + +def test_execute_memory_command_views_memory(manager): + output = execute_memory_command(manager, ["user-role"]) + assert output == "[user] Role\n\nSenior cloud engineer" + + +def test_execute_memory_command_missing_memory(manager): + output = execute_memory_command(manager, ["missing"]) + assert output == "Memory 'missing' not found." + + +def test_execute_memory_command_searches_memories(manager): + output = execute_memory_command(manager, ["search", "integration"]) + assert output == "Matching memories:\n - feedback-testing - Testing" + + +def test_execute_memory_command_search_no_matches(manager): + output = execute_memory_command(manager, ["search", "nope"]) + assert output == "No matching memories." + + +def test_execute_memory_command_search_without_query_shows_help(manager): + output = execute_memory_command(manager, ["search"]) + assert "Usage: /memory" in output + + +def test_execute_memory_command_deletes_memory(manager): + output = execute_memory_command(manager, ["delete", "user-role"]) + assert output == "Memory 'user-role' deleted." + assert manager.load("user-role") is None + + +def test_execute_memory_command_delete_missing(manager): + output = execute_memory_command(manager, ["delete", "missing"]) + assert output == "Memory 'missing' not found." + + +def test_execute_memory_command_invalid_name(manager): + output = execute_memory_command(manager, ["../escape"]) + assert "Invalid memory name" in output + + +def test_execute_memory_command_help_and_unknown_multi_token(manager): + assert "Usage: /memory" in execute_memory_command(manager, ["help"]) + assert "Usage: /memory" in execute_memory_command(manager, ["remove", "user-role"]) + + +@pytest.mark.asyncio +async def test_memory_command_uses_repl_memory_manager(manager): + output = await memory_command(context=_Context(manager), args=["user-role"]) + assert output == "[user] Role\n\nSenior cloud engineer" + + +@pytest.mark.asyncio +async def test_memory_command_missing_context_manager(): + output = await memory_command(context=object(), args=[]) + assert output == "Memory manager is unavailable." diff --git a/tests/commands/test_registry.py b/tests/commands/test_registry.py index 3a8cedd..af10b8d 100644 --- a/tests/commands/test_registry.py +++ b/tests/commands/test_registry.py @@ -199,18 +199,29 @@ def test_create_default_registry_returns_registry(self): registry = create_default_registry() assert isinstance(registry, CommandRegistry) - def test_create_default_registry_has_9_commands(self): - """Test create_default_registry has 9 commands.""" + def test_create_default_registry_has_10_commands(self): + """Test create_default_registry has 10 commands.""" registry = create_default_registry() all_cmds = registry.get_all() - assert len(all_cmds) == 9 + assert len(all_cmds) == 10 def test_create_default_registry_command_names(self): """Test create_default_registry has expected command names.""" registry = create_default_registry() all_cmds = registry.get_all() names = {c.name for c in all_cmds} - assert names == {"help", "clear", "model", "compact", "exit", "auth", "debug", "effort", "resume"} + assert names == { + "help", + "clear", + "model", + "compact", + "exit", + "auth", + "debug", + "effort", + "resume", + "memory", + } def test_help_command_has_alias(self): """Test help command has ? alias.""" diff --git a/tests/memory/test_memory_manager.py b/tests/memory/test_memory_manager.py index 03c5083..61f3e1c 100644 --- a/tests/memory/test_memory_manager.py +++ b/tests/memory/test_memory_manager.py @@ -91,3 +91,68 @@ def test_legacy_invalid_memory_file_does_not_break_listing_or_index_update(self, assert [memory["name"] for memory in memories] == ["old memory"] assert "old memory" in manager.get_index_content() assert "new-safe" in manager.get_index_content() + + def test_search_matches_name_description_type_and_content_case_insensitive(self, manager): + manager.save("user-role", content="Senior cloud engineer", memory_type="user", description="Role") + manager.save("feedback-testing", content="Prefer integration tests", memory_type="feedback", description="QA") + manager.save("project-deadline", content="Freeze on 2026-06-15", memory_type="project", description="Schedule") + + assert [m["name"] for m in manager.search("ROLE")] == ["user-role"] + assert [m["name"] for m in manager.search("integration")] == ["feedback-testing"] + assert [m["name"] for m in manager.search("project")] == ["project-deadline"] + assert [m["name"] for m in manager.search("schedule")] == ["project-deadline"] + + def test_search_empty_query_and_no_matches(self, manager): + manager.save("user-role", content="Senior cloud engineer", memory_type="user", description="Role") + + assert manager.search("") == [] + assert manager.search(" ") == [] + assert manager.search("does-not-exist") == [] + + @pytest.mark.skipif(sys.platform == "win32", reason="Symlink permissions vary on Windows") + def test_list_and_search_ignore_symlinked_memory_files(self, manager, tmp_path): + outside = tmp_path.parent / "outside.md" + outside.write_text("---\nname: leaked\ndescription: Secret\ntype: user\n---\n\nsecret outside content\n") + (tmp_path / "leaked.md").symlink_to(outside) + + assert manager.list_memories() == [] + assert manager.search("secret") == [] + + @pytest.mark.skipif(sys.platform == "win32", reason="Symlink permissions vary on Windows") + def test_load_does_not_follow_symlinked_memory_file(self, manager, tmp_path): + outside = tmp_path.parent / "outside.md" + outside.write_text("---\nname: leaked\ndescription: Secret\ntype: user\n---\n\nsecret outside content\n") + (tmp_path / "leaked.md").symlink_to(outside) + + assert manager.load("leaked") is None + + @pytest.mark.skipif(sys.platform == "win32", reason="Symlink permissions vary on Windows") + def test_save_does_not_overwrite_symlinked_memory_file(self, manager, tmp_path): + outside = tmp_path.parent / "outside.md" + outside.write_text("original") + (tmp_path / "leaked.md").symlink_to(outside) + + with pytest.raises(ValueError, match="Invalid memory path"): + manager.save("leaked", content="new", memory_type="user", description="bad") + + assert outside.read_text() == "original" + + @pytest.mark.skipif(sys.platform == "win32", reason="Symlink permissions vary on Windows") + def test_get_index_content_does_not_follow_symlinked_index(self, manager, tmp_path): + outside = tmp_path.parent / "outside-index.md" + outside.write_text("secret index") + (tmp_path / "MEMORY.md").symlink_to(outside) + + assert manager.get_index_content() == "" + + @pytest.mark.skipif(sys.platform == "win32", reason="Symlink permissions vary on Windows") + def test_save_does_not_overwrite_symlinked_index(self, manager, tmp_path): + outside = tmp_path.parent / "outside-index.md" + outside.write_text("original index") + (tmp_path / "MEMORY.md").symlink_to(outside) + + with pytest.raises(ValueError, match="Invalid memory path"): + manager.save("safe", content="content", memory_type="user", description="safe") + + assert outside.read_text() == "original index" + assert not (tmp_path / "safe.md").exists() diff --git a/tests/memory/test_memory_tools.py b/tests/memory/test_memory_tools.py index f3fcc92..a14cc21 100644 --- a/tests/memory/test_memory_tools.py +++ b/tests/memory/test_memory_tools.py @@ -143,3 +143,10 @@ async def test_write_memory_returns_error_for_invalid_name(self, tmp_path): async def test_is_read_only(self): assert WriteMemoryTool(FakeMemoryManager()).is_read_only() is False + + async def test_description_guides_explicit_remember_requests(self): + description = WriteMemoryTool(FakeMemoryManager()).description + + assert "explicitly asks" in description + assert "remember" in description + assert "concise" in description diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 92d7336..9dac73e 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -20,6 +20,23 @@ POT_FILE = I18N_DIR / "messages.pot" LOCALES_DIR = I18N_DIR / "locales" +MEMORY_COMMAND_MSGIDS = { + "Usage: /memory [|search |delete |help]", + "Saved memories:", + "No memories saved yet.", + "Matching memories:", + "No matching memories.", + "Memory '{name}' not found.", + "Memory '{name}' deleted.", + "Memory manager is unavailable.", + "View and manage persistent memories", + "[|search |delete |help]", + "Search saved memories", + "Delete a saved memory", + "Show memory command help", + "Saved memory", +} + def _get_all_msgids_from_pot(pot_file: Path) -> set[str]: """Extract all msgids from a .pot template file. @@ -276,6 +293,31 @@ def test_translation_completeness(): pytest.fail("\n".join(error_messages)) +@pytest.mark.skipif(sys.platform == "win32", reason="messages.pot not generated on Windows") +def test_memory_command_translations_are_complete(): + """Verify /memory-specific strings are translated, not copied as placeholders.""" + assert POT_FILE.exists(), f"POT file not found at {POT_FILE}" + pot_msgids = _get_all_msgids_from_pot(POT_FILE) + missing_from_pot = MEMORY_COMMAND_MSGIDS - pot_msgids + assert not missing_from_pot, f"/memory msgids missing from messages.pot: {sorted(missing_from_pot)}" + + language_dirs = _discover_language_dirs() + assert language_dirs, "No language directories found" + + errors = [] + for lang_dir in language_dirs: + po_file = lang_dir / "LC_MESSAGES" / "messages.po" + translations = _get_all_translations_from_po(po_file) + for msgid in sorted(MEMORY_COMMAND_MSGIDS): + msgstr = translations.get(msgid, "").strip() + if not msgstr: + errors.append(f"{lang_dir.name}: missing translation for {msgid!r}") + elif msgstr == msgid: + errors.append(f"{lang_dir.name}: untranslated placeholder for {msgid!r}") + + assert not errors, "\n".join(errors) + + class TestDetectWindowsUILanguage: """_detect_windows_ui_language wraps GetUserDefaultLocaleName via ctypes.""" diff --git a/tests/ui/suggestions/test_aggregator.py b/tests/ui/suggestions/test_aggregator.py index cab3c4c..d3c91bc 100644 --- a/tests/ui/suggestions/test_aggregator.py +++ b/tests/ui/suggestions/test_aggregator.py @@ -19,6 +19,16 @@ def aggregator(command_provider) -> SuggestionAggregator: return SuggestionAggregator([command_provider]) +class _MemoryManager: + def list_memories(self): + return [{"name": "user-role", "description": "Role", "type": "user", "content": "Senior engineer"}] + + +@pytest.fixture +def memory_aggregator() -> SuggestionAggregator: + return SuggestionAggregator([CommandProvider(create_default_registry(), memory_manager=_MemoryManager())]) + + class TestSuggestionAggregator: def test_update_with_slash_trigger(self, aggregator): """/mod → suggestions > 0.""" @@ -156,3 +166,16 @@ def test_accept_ghost_text_does_not_include_hint(self, aggregator): assert result is not None text, _start, _end = result assert text == "/debug " + + def test_memory_argument_ghost_text(self, memory_aggregator): + """/memory d → ghost text completes the delete action.""" + memory_aggregator.update("/memory d", 9) + assert memory_aggregator.ghost_text == "elete " + + def test_accept_memory_argument_suggestion_replaces_command_span(self, memory_aggregator): + """/memory delete suggestion replaces the full slash command token.""" + text = "/memory delete " + memory_aggregator.update(text, len(text)) + result = memory_aggregator.accept_selected() + + assert result == ("/memory delete user-role", 0, len(text)) diff --git a/tests/ui/suggestions/test_command_provider.py b/tests/ui/suggestions/test_command_provider.py index c35037a..a190b98 100644 --- a/tests/ui/suggestions/test_command_provider.py +++ b/tests/ui/suggestions/test_command_provider.py @@ -20,6 +20,24 @@ def provider(registry) -> CommandProvider: return CommandProvider(registry) +class _MemoryManager: + def list_memories(self): + return [ + {"name": "user-role", "description": "Role", "type": "user", "content": "Senior engineer"}, + { + "name": "feedback-testing", + "description": "Testing", + "type": "feedback", + "content": "Prefer integration tests", + }, + ] + + +@pytest.fixture +def memory_provider(registry) -> CommandProvider: + return CommandProvider(registry, memory_manager=_MemoryManager()) + + def make_token(text: str, start: int = 0) -> CompletionToken: return CompletionToken(text=text, start=start, end=start + len(text), trigger="/") @@ -101,3 +119,36 @@ def test_arg_hint_absent_for_commands_without_one(self, provider): clear_items = [i for i in items if i.display_text == "clear"] assert len(clear_items) == 1 assert clear_items[0].arg_hint is None + + def test_memory_second_item_suggests_actions_and_memory_names(self, memory_provider): + """/memory → subcommands plus saved memory names.""" + token = make_token("/memory ") + items = memory_provider.provide(token) + + names = {item.display_text for item in items} + assert {"search", "delete", "help", "user-role", "feedback-testing"}.issubset(names) + assert [item.completion for item in items if item.display_text == "search"] == ["/memory search "] + assert [item.completion for item in items if item.display_text == "user-role"] == ["/memory user-role"] + + def test_memory_second_item_filters_action_prefix(self, memory_provider): + """/memory d → delete action suggestion.""" + token = make_token("/memory d") + items = memory_provider.provide(token) + + assert [item.display_text for item in items] == ["delete"] + assert items[0].completion == "/memory delete " + + def test_memory_delete_suggests_memory_names(self, memory_provider): + """/memory delete → saved memory name suggestions.""" + token = make_token("/memory delete ") + items = memory_provider.provide(token) + + names = [item.display_text for item in items] + assert names == ["feedback-testing", "user-role"] + assert items[0].completion == "/memory delete feedback-testing" + assert all(item.id.startswith("cmd:memory:") for item in items) + + def test_memory_search_query_has_no_argument_suggestions(self, memory_provider): + """/memory search leaves free-form search input alone.""" + token = make_token("/memory search ") + assert memory_provider.provide(token) == [] diff --git a/tests/ui/suggestions/test_token_extractor.py b/tests/ui/suggestions/test_token_extractor.py index dcaa8ba..d6cd330 100644 --- a/tests/ui/suggestions/test_token_extractor.py +++ b/tests/ui/suggestions/test_token_extractor.py @@ -102,6 +102,26 @@ def test_slash_after_tab(self, extractor): assert token is not None assert token.trigger == "/" + def test_slash_command_token_includes_trailing_space(self, extractor): + """/memory stays active for argument suggestions.""" + text = "/memory " + token = extractor.extract(text, len(text)) + assert token is not None + assert token.trigger == "/" + assert token.text == "/memory " + assert token.start == 0 + assert token.end == len(text) + + def test_slash_command_token_includes_arguments(self, extractor): + """Slash command suggestions can inspect the second input item.""" + text = "run /memory delete " + token = extractor.extract(text, len(text)) + assert token is not None + assert token.trigger == "/" + assert token.text == "/memory delete " + assert token.start == 4 + assert token.end == len(text) + def test_token_start_and_end_positions(self, extractor): """Verify start and end positions are correct.""" text = "look at @config" From b265345b5b74341a2772fadf850b4e4c7bda0448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 11:01:30 +0800 Subject: [PATCH 02/11] feat: add status command and usage tracking --- src/iac_code/agent/agent_loop.py | 57 +++++ src/iac_code/commands/__init__.py | 9 + src/iac_code/commands/status.py | 98 ++++++++ .../i18n/locales/de/LC_MESSAGES/messages.po | 187 +++++++++++---- .../i18n/locales/es/LC_MESSAGES/messages.po | 187 +++++++++++---- .../i18n/locales/fr/LC_MESSAGES/messages.po | 187 +++++++++++---- .../i18n/locales/ja/LC_MESSAGES/messages.po | 187 +++++++++++---- .../i18n/locales/pt/LC_MESSAGES/messages.po | 187 +++++++++++---- .../i18n/locales/zh/LC_MESSAGES/messages.po | 187 +++++++++++---- src/iac_code/providers/manager.py | 22 ++ src/iac_code/services/session_index.py | 8 +- src/iac_code/services/session_storage.py | 5 +- src/iac_code/services/session_usage.py | 168 +++++++++++++ src/iac_code/ui/repl.py | 68 +++++- src/iac_code/utils/project_paths.py | 5 + tests/agent/test_agent_loop_new.py | 224 +++++++++++++++++ tests/commands/test_registry.py | 7 +- tests/commands/test_status.py | 227 ++++++++++++++++++ tests/providers/test_manager.py | 12 + tests/services/test_session_index.py | 15 ++ tests/services/test_session_storage.py | 14 ++ tests/services/test_session_usage.py | 88 +++++++ tests/test_i18n.py | 11 +- tests/ui/test_repl_status.py | 98 ++++++++ 24 files changed, 1939 insertions(+), 319 deletions(-) create mode 100644 src/iac_code/commands/status.py create mode 100644 src/iac_code/services/session_usage.py create mode 100644 tests/commands/test_status.py create mode 100644 tests/services/test_session_usage.py create mode 100644 tests/ui/test_repl_status.py diff --git a/src/iac_code/agent/agent_loop.py b/src/iac_code/agent/agent_loop.py index bf4deb1..3f682ec 100644 --- a/src/iac_code/agent/agent_loop.py +++ b/src/iac_code/agent/agent_loop.py @@ -15,6 +15,7 @@ from iac_code.agent.message import ContentBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock from iac_code.i18n import _ from iac_code.services.context_manager import ContextManager +from iac_code.services.session_usage import SessionUsageStore, SessionUsageTotals from iac_code.tools.base import ToolContext, ToolRegistry, ToolResult from iac_code.tools.result_storage import ResultStorage from iac_code.tools.tool_executor import ToolCallRequest, ToolExecutor @@ -65,6 +66,7 @@ def __init__( tool_registry: ToolRegistry, max_turns: int = 100, session_storage: Any = None, # SessionStorage + session_usage_store: SessionUsageStore | None = None, session_id: str | None = None, resume_messages: list | None = None, cwd: str | None = None, @@ -79,6 +81,8 @@ def __init__( self._session_storage = session_storage self._session_id = session_id or str(uuid.uuid4())[:8] self._cwd = cwd or os.getcwd() + self._session_usage_store = session_usage_store or SessionUsageStore() + self._session_usage_totals = self._session_usage_store.load(self._cwd, self._session_id) self._permission_context = permission_context self._permission_context_getter = permission_context_getter self._auto_trigger_skills = auto_trigger_skills or [] @@ -250,6 +254,7 @@ async def run_streaming(self, user_input: str | list[ContentBlock]) -> AsyncGene final_text_chunks.append(event.text) if isinstance(event, MessageEndEvent): final_stop_reason = event.stop_reason + self._record_session_usage(event.usage) yield event except asyncio.CancelledError: log_event(Events.SESSION_CANCELLED, {"stage": "in_query"}) @@ -609,6 +614,7 @@ async def _auto_compact(self) -> CompactionEvent | None: messages=[ProviderMessage.user(compaction_prompt)], system="You are a helpful assistant that summarizes conversations concisely.", ) + self._record_response_usage(response) if response.text: original, new = self.context_manager.apply_compaction(response.text) duration_ms = int((time.monotonic() - started) * 1000) @@ -650,6 +656,7 @@ async def compact(self) -> CompactResult: messages=[ProviderMessage.user(compaction_prompt)], system="You are a helpful assistant that summarizes conversations concisely.", ) + self._record_response_usage(response) if response.text: original, compacted = self.context_manager.apply_compaction(response.text) return CompactResult( @@ -691,6 +698,7 @@ def replace_session(self, session_id: str, resume_messages: list | None) -> None self.context_manager.reset() if resume_messages: self.context_manager.load_messages(resume_messages) + self._session_usage_totals = self._session_usage_store.load(self._cwd, self._session_id) self._result_storage = ResultStorage( storage_dir=os.path.join(str(get_config_dir()), "tool-results", session_id), ) @@ -712,5 +720,54 @@ def reset(self) -> None: self._auto_loaded_skills.clear() self.context_manager.reset() + @property + def session_id(self) -> str: + return self._session_id + + @property + def max_turns(self) -> int: + return self._max_turns + def get_context_usage(self) -> dict: return self.context_manager.get_usage() + + def get_session_usage(self) -> SessionUsageTotals: + return self._session_usage_totals.copy() + + def _record_session_usage(self, usage: Usage) -> None: + if not self._session_usage_totals.add(usage): + return + + provider = self._get_runtime_provider_key() + model = self._provider_manager.get_model_name() if hasattr(self._provider_manager, "get_model_name") else "" + try: + self._session_usage_store.append( + self._cwd, + self._session_id, + usage, + provider=provider, + model=model, + ) + except Exception as exc: + logger.debug("Failed to persist session usage for {}: {}", self._session_id, exc) + + def _record_response_usage(self, response: Any) -> None: + usage = getattr(response, "usage", None) + if isinstance(usage, Usage): + self._record_session_usage(usage) + + def _get_runtime_provider_key(self) -> str: + if hasattr(self._provider_manager, "get_provider_key"): + try: + provider_key = self._provider_manager.get_provider_key() + except Exception: + pass + else: + if isinstance(provider_key, str): + return provider_key + try: + from iac_code.config import get_active_provider_key + + return get_active_provider_key() or "" + except Exception: + return "" diff --git a/src/iac_code/commands/__init__.py b/src/iac_code/commands/__init__.py index 8bbdc49..e8c18be 100644 --- a/src/iac_code/commands/__init__.py +++ b/src/iac_code/commands/__init__.py @@ -11,6 +11,7 @@ from iac_code.commands.model import model_command from iac_code.commands.registry import Command, CommandRegistry, LocalCommand, PromptCommand from iac_code.commands.resume import resume_command +from iac_code.commands.status import status_command from iac_code.i18n import _ @@ -106,6 +107,14 @@ def create_default_registry() -> CommandRegistry: history_mode="session", ) ) + registry.register( + LocalCommand( + name="status", + description=_("Show current session status"), + handler=status_command, + history_mode="session", + ) + ) return registry diff --git a/src/iac_code/commands/status.py b/src/iac_code/commands/status.py new file mode 100644 index 0000000..6d14f8b --- /dev/null +++ b/src/iac_code/commands/status.py @@ -0,0 +1,98 @@ +"""Status command - show current session state and recorded API usage.""" + +from __future__ import annotations + +from typing import Any + +from rich.cells import cell_len +from rich.console import Group +from rich.panel import Panel +from rich.text import Text + +from iac_code.i18n import _ + +LABEL_COLUMN_WIDTH = 12 + + +async def status_command(context=None, **kwargs) -> str | None: + if context is None: + return _("Status command requires a context.") + repl = getattr(context, "repl", None) + if repl is None: + return _("Status command requires a REPL context.") + if not hasattr(repl, "get_status_snapshot"): + return _("Status is only available in interactive mode.") + + snapshot = repl.get_status_snapshot() + context.console.print(_render_status_panel(snapshot)) + return None + + +def _render_status_panel(snapshot: dict[str, Any]) -> Panel: + text = Text() + _append_line(text, _("Session"), _session_display(snapshot)) + _append_line(text, _("Provider"), snapshot.get("provider") or _("not configured")) + _append_line(text, _("Model"), snapshot.get("model") or _("not configured")) + _append_line(text, _("Region"), snapshot.get("region") or _("not configured")) + _append_line(text, _("CWD"), snapshot.get("cwd") or "") + text.append("\n") + + usage = snapshot.get("api_usage") + text.append(_("API Token Usage (recorded):"), style="bold") + text.append("\n") + if usage is not None and getattr(usage, "has_recorded_usage", False): + _append_line(text, _("Input"), _format_int(getattr(usage, "input_tokens", 0)), indent=2) + _append_line(text, _("Output"), _format_int(getattr(usage, "output_tokens", 0)), indent=2) + _append_line(text, _("Cache read"), _format_int(getattr(usage, "cache_read_input_tokens", 0)), indent=2) + _append_line(text, _("Total"), _format_int(getattr(usage, "total_tokens", 0)), indent=2) + else: + text.append(" ") + text.append(_("No recorded API usage for this session yet."), style="dim") + text.append("\n") + text.append("\n") + + _append_line(text, _("Turns"), "{} / {}".format(snapshot.get("turn_count", 0), snapshot.get("max_turns", 0))) + _append_line(text, _("Context"), _format_context(snapshot.get("context_usage") or {})) + + return Panel(Group(text), title=_("Session Status"), border_style="cyan", expand=False) + + +def _append_line(text: Text, label: str, value: str, *, indent: int = 0) -> None: + label_text = label + ":" + padding = max(0, LABEL_COLUMN_WIDTH - cell_len(label_text)) + text.append(" " * indent) + text.append(label_text, style="bold") + text.append(" " * (padding + 1)) + text.append(str(value)) + text.append("\n") + + +def _session_display(snapshot: dict[str, Any]) -> str: + session_id = snapshot.get("session_id") or "" + if snapshot.get("resumed"): + return _("{session_id} (resumed)").format(session_id=session_id) + return str(session_id) + + +def _format_context(context_usage: dict[str, Any]) -> str: + percent = float(context_usage.get("usage_percent") or 0) + total = int(context_usage.get("total_tokens") or 0) + window = int(context_usage.get("context_window") or 0) + return _("{percent} used ({total} / {window})").format( + percent=f"{percent:.0f}%", + total=_format_compact(total), + window=_format_compact(window), + ) + + +def _format_int(value: int) -> str: + return f"{int(value):,}" + + +def _format_compact(value: int) -> str: + value = int(value) + if value >= 1_000_000: + return f"{value / 1_000_000:.1f}M" + if value >= 1_000: + return f"{value // 1000}k" + return str(value) 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 3dd4fac..dc2474e 100644 --- a/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.0\n" +"Project-Id-Version: iac-code 0.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 11:24+0800\n" +"POT-Creation-Date: 2026-06-02 20:13+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: de\n" @@ -112,8 +112,8 @@ msgstr "Verwendung: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "Der Speicher-Manager ist nicht verfügbar." -#: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 -#: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 +#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 +#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 msgid "Permission denied." msgstr "Zugriff verweigert." @@ -614,58 +614,62 @@ msgstr "Verzeichnis für persistierte A2A-Routen" msgid "Save the provided routes as a route snapshot" msgstr "Speichert die angegebenen Routen als Routen-Snapshot" -#: src/iac_code/commands/__init__.py:23 +#: src/iac_code/commands/__init__.py:24 msgid "Show available commands" msgstr "Verfügbare Befehle anzeigen" -#: src/iac_code/commands/__init__.py:32 +#: src/iac_code/commands/__init__.py:33 msgid "Clear conversation history" msgstr "Konversationsverlauf löschen" -#: src/iac_code/commands/__init__.py:40 +#: src/iac_code/commands/__init__.py:41 msgid "Show or switch model" msgstr "Modell anzeigen oder wechseln" -#: src/iac_code/commands/__init__.py:49 +#: src/iac_code/commands/__init__.py:50 msgid "Show or switch thinking effort" msgstr "Thinking-Effort anzeigen oder wechseln" -#: src/iac_code/commands/__init__.py:58 +#: src/iac_code/commands/__init__.py:59 msgid "Compact conversation context" msgstr "Konversationskontext komprimieren" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compacting conversation" msgstr "Konversation wird komprimiert" -#: src/iac_code/commands/__init__.py:67 +#: src/iac_code/commands/__init__.py:68 msgid "Exit the application" msgstr "Anwendung beenden" -#: src/iac_code/commands/__init__.py:76 +#: src/iac_code/commands/__init__.py:77 msgid "Authenticate with LLM provider" msgstr "Beim LLM-Anbieter authentifizieren" -#: src/iac_code/commands/__init__.py:85 +#: src/iac_code/commands/__init__.py:86 msgid "Toggle debug logging" msgstr "Debug-Protokollierung umschalten" -#: src/iac_code/commands/__init__.py:94 +#: src/iac_code/commands/__init__.py:95 msgid "View and manage persistent memories" msgstr "Persistente Erinnerungen anzeigen und verwalten" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:103 +#: src/iac_code/commands/__init__.py:104 msgid "Resume a previous session" msgstr "Eine frühere Sitzung fortsetzen" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "[conversation id or search term]" msgstr "[Konversations-ID oder Suchbegriff]" +#: src/iac_code/commands/__init__.py:113 +msgid "Show current session status" +msgstr "Aktuellen Sitzungsstatus anzeigen" + #: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" @@ -845,7 +849,8 @@ msgid "Credential" msgstr "Anmeldedaten" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Region" @@ -1052,6 +1057,85 @@ msgstr "Sitzung nicht gefunden: {arg}" msgid "Resume cancelled" msgstr "Fortsetzen abgebrochen" +#: src/iac_code/commands/status.py:16 +msgid "Status command requires a context." +msgstr "Der Befehl status benötigt einen Kontext." + +#: src/iac_code/commands/status.py:19 +msgid "Status command requires a REPL context." +msgstr "Der Befehl status benötigt einen REPL-Kontext." + +#: src/iac_code/commands/status.py:21 +msgid "Status is only available in interactive mode." +msgstr "status ist nur im interaktiven Modus verfügbar." + +#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +msgid "Session" +msgstr "Sitzung" + +#: src/iac_code/commands/status.py:31 +msgid "Provider" +msgstr "Anbieter" + +#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:33 +msgid "not configured" +msgstr "nicht konfiguriert" + +#: src/iac_code/commands/status.py:32 +msgid "Model" +msgstr "Modell" + +#: src/iac_code/commands/status.py:34 +msgid "CWD" +msgstr "Aktuelles Verzeichnis" + +#: src/iac_code/commands/status.py:38 +msgid "API Token Usage (recorded):" +msgstr "API-Token-Nutzung (aufgezeichnet):" + +#: src/iac_code/commands/status.py:41 +msgid "Input" +msgstr "Eingabe" + +#: src/iac_code/commands/status.py:42 +msgid "Output" +msgstr "Ausgabe" + +#: src/iac_code/commands/status.py:43 +msgid "Cache read" +msgstr "Cache-Lesezugriffe" + +#: src/iac_code/commands/status.py:44 +msgid "Total" +msgstr "Gesamt" + +#: src/iac_code/commands/status.py:47 +msgid "No recorded API usage for this session yet." +msgstr "Für diese Sitzung wurde noch keine API-Nutzung aufgezeichnet." + +#: src/iac_code/commands/status.py:51 +msgid "Turns" +msgstr "Runden" + +#: src/iac_code/commands/status.py:52 +msgid "Context" +msgstr "Kontext" + +#: src/iac_code/commands/status.py:54 +msgid "Session Status" +msgstr "Sitzungsstatus" + +#: src/iac_code/commands/status.py:68 +#, python-brace-format +msgid "{session_id} (resumed)" +msgstr "{session_id} (fortgesetzt)" + +#: src/iac_code/commands/status.py:76 +#, python-brace-format +msgid "{percent} used ({total} / {window})" +msgstr "{percent} verwendet ({total} / {window})" + # Typer/Click built-in strings #: src/iac_code/i18n/__init__.py:51 msgid "Options" @@ -1523,7 +1607,7 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "{action} wird aufgerufen …" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:390 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Aufruf erfolgreich" @@ -1687,7 +1771,7 @@ msgstr "IMPORT FEHLGESCHLAGEN" msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:389 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "Aufruf erfolgreich (RequestId: {request_id})" @@ -1856,10 +1940,6 @@ msgstr "Ihr KI-gestützter Infrastructure-as-Code-Assistent" msgid "Welcome back" msgstr "Willkommen zurück" -#: src/iac_code/ui/banner.py:138 -msgid "Session" -msgstr "Sitzung" - #: src/iac_code/ui/banner.py:148 msgid "Debug mode" msgstr "Debug-Modus" @@ -1961,103 +2041,103 @@ msgstr "Nein, immer \"{rule}\" ablehnen (diese Sitzung)" msgid "No, always reject this tool" msgstr "Nein, dieses Tool immer ablehnen" -#: src/iac_code/ui/repl.py:370 +#: src/iac_code/ui/repl.py:374 msgid "Press Ctrl+C again to exit." msgstr "Drücken Sie erneut Ctrl+C zum Beenden." -#: src/iac_code/ui/repl.py:395 +#: src/iac_code/ui/repl.py:399 msgid "Interrupted." msgstr "Unterbrochen." -#: src/iac_code/ui/repl.py:432 +#: src/iac_code/ui/repl.py:436 msgid "Goodbye!" msgstr "Auf Wiedersehen!" -#: src/iac_code/ui/repl.py:433 +#: src/iac_code/ui/repl.py:437 msgid "Resume this session with:" msgstr "Diese Sitzung fortsetzen mit:" -#: src/iac_code/ui/repl.py:458 +#: src/iac_code/ui/repl.py:462 msgid "Update now" msgstr "Jetzt aktualisieren" -#: src/iac_code/ui/repl.py:460 +#: src/iac_code/ui/repl.py:464 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Führt den angezeigten Aktualisierungsbefehl aus und beendet das Programm " "bei Erfolg." -#: src/iac_code/ui/repl.py:463 +#: src/iac_code/ui/repl.py:467 msgid "Skip" msgstr "Überspringen" -#: src/iac_code/ui/repl.py:465 +#: src/iac_code/ui/repl.py:469 msgid "Continue with the current version for this session." msgstr "Für diese Sitzung mit der aktuellen Version fortfahren." -#: src/iac_code/ui/repl.py:468 +#: src/iac_code/ui/repl.py:472 msgid "Skip until next version" msgstr "Bis zur nächsten Version überspringen" -#: src/iac_code/ui/repl.py:470 +#: src/iac_code/ui/repl.py:474 msgid "Hide this update until a newer version is available." msgstr "Dieses Update ausblenden, bis eine neuere Version verfügbar ist." -#: src/iac_code/ui/repl.py:489 src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 msgid "Update command failed. Continuing with the current version." msgstr "" "Der Aktualisierungsbefehl ist fehlgeschlagen. Es wird mit der aktuellen " "Version fortgefahren." -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:498 msgid "Update completed. Restart iac-code to continue." msgstr "Update abgeschlossen. Starten Sie iac-code neu, um fortzufahren." -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:536 msgid "No image in clipboard." msgstr "Kein Bild in der Zwischenablage." -#: src/iac_code/ui/repl.py:718 +#: src/iac_code/ui/repl.py:722 msgid "Usage: !" msgstr "Verwendung: !" -#: src/iac_code/ui/repl.py:723 +#: src/iac_code/ui/repl.py:727 msgid "Shell command support is unavailable." msgstr "Shell-Befehlsunterstützung ist nicht verfügbar." -#: src/iac_code/ui/repl.py:787 +#: src/iac_code/ui/repl.py:791 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "Unbekannter Skill: ${name}. Tippe /, um Befehle und Skills aufzulisten." -#: src/iac_code/ui/repl.py:789 +#: src/iac_code/ui/repl.py:793 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available commands.Unbekannter " "Befehl: /{name}. Geben Sie /help für verfügbare Befehle ein." -#: src/iac_code/ui/repl.py:794 +#: src/iac_code/ui/repl.py:798 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ ruft nur Skills auf. Verwende stattdessen /{name}." -#: src/iac_code/ui/repl.py:816 src/iac_code/ui/repl.py:861 +#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 #, python-brace-format msgid "Command error: {error}" msgstr "Befehlsfehler: {error}" -#: src/iac_code/ui/repl.py:823 +#: src/iac_code/ui/repl.py:827 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Kein Handler für Befehl: {name}" -#: src/iac_code/ui/repl.py:1128 +#: src/iac_code/ui/repl.py:1132 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sitzung nicht gefunden: {session_id}" -#: src/iac_code/ui/repl.py:1147 +#: src/iac_code/ui/repl.py:1151 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2068,19 +2148,19 @@ msgstr "" "Zum Fortsetzen ausführen:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1252 msgid "This conversation is from a different directory." msgstr "Diese Konversation stammt aus einem anderen Verzeichnis." -#: src/iac_code/ui/repl.py:1188 +#: src/iac_code/ui/repl.py:1254 msgid "To resume, run:" msgstr "Zum Fortsetzen ausführen:" -#: src/iac_code/ui/repl.py:1193 +#: src/iac_code/ui/repl.py:1259 msgid "(Command copied to clipboard)" msgstr "(Befehl in die Zwischenablage kopiert)" -#: src/iac_code/ui/repl.py:1350 +#: src/iac_code/ui/repl.py:1416 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2089,12 +2169,12 @@ msgstr "" "Das aktuelle Modell {model} unterstützt keine Bildeingabe. Verwenden Sie " "/model, um zu einem Vision-fähigen Modell zu wechseln." -#: src/iac_code/ui/repl.py:1359 +#: src/iac_code/ui/repl.py:1425 #, python-brace-format msgid "Image error: {err}" msgstr "Bildfehler: {err}" -#: src/iac_code/ui/repl.py:1376 +#: src/iac_code/ui/repl.py:1442 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2361,3 +2441,6 @@ msgstr "" #~ msgid " Option 2 - npmmirror (China-friendly mirror):" #~ msgstr " Option 2 - npmmirror (China-freundlicher Spiegel):" +#~ msgid "Cache create" +#~ msgstr "Cache-Erstellung" + 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 4a6c445..8f0c861 100644 --- a/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.0\n" +"Project-Id-Version: iac-code 0.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 11:24+0800\n" +"POT-Creation-Date: 2026-06-02 20:13+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: es\n" @@ -115,8 +115,8 @@ msgstr "Uso: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "El gestor de memoria no está disponible." -#: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 -#: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 +#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 +#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 msgid "Permission denied." msgstr "Permiso denegado." @@ -613,58 +613,62 @@ msgstr "Directorio para rutas A2A persistentes" msgid "Save the provided routes as a route snapshot" msgstr "Guarda las rutas proporcionadas como una instantánea de rutas" -#: src/iac_code/commands/__init__.py:23 +#: src/iac_code/commands/__init__.py:24 msgid "Show available commands" msgstr "Mostrar los comandos disponibles" -#: src/iac_code/commands/__init__.py:32 +#: src/iac_code/commands/__init__.py:33 msgid "Clear conversation history" msgstr "Borrar el historial de conversación" -#: src/iac_code/commands/__init__.py:40 +#: src/iac_code/commands/__init__.py:41 msgid "Show or switch model" msgstr "Mostrar o cambiar de modelo" -#: src/iac_code/commands/__init__.py:49 +#: src/iac_code/commands/__init__.py:50 msgid "Show or switch thinking effort" msgstr "Mostrar o cambiar el nivel de razonamiento (effort)" -#: src/iac_code/commands/__init__.py:58 +#: src/iac_code/commands/__init__.py:59 msgid "Compact conversation context" msgstr "Compactar el contexto de la conversación" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compacting conversation" msgstr "Compactando la conversación" -#: src/iac_code/commands/__init__.py:67 +#: src/iac_code/commands/__init__.py:68 msgid "Exit the application" msgstr "Salir de la aplicación" -#: src/iac_code/commands/__init__.py:76 +#: src/iac_code/commands/__init__.py:77 msgid "Authenticate with LLM provider" msgstr "Autenticar con el proveedor LLM" -#: src/iac_code/commands/__init__.py:85 +#: src/iac_code/commands/__init__.py:86 msgid "Toggle debug logging" msgstr "Activar o desactivar el registro de depuración" -#: src/iac_code/commands/__init__.py:94 +#: src/iac_code/commands/__init__.py:95 msgid "View and manage persistent memories" msgstr "Ver y administrar memorias persistentes" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:103 +#: src/iac_code/commands/__init__.py:104 msgid "Resume a previous session" msgstr "Reanudar una sesión anterior" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "[conversation id or search term]" msgstr "[id de conversación o término de búsqueda]" +#: src/iac_code/commands/__init__.py:113 +msgid "Show current session status" +msgstr "Mostrar el estado actual de la sesión" + #: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" @@ -844,7 +848,8 @@ msgid "Credential" msgstr "Credencial" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Región" @@ -1053,6 +1058,85 @@ msgstr "Sesión no encontrada: {arg}" msgid "Resume cancelled" msgstr "Reanudación cancelada" +#: src/iac_code/commands/status.py:16 +msgid "Status command requires a context." +msgstr "El comando status requiere un contexto." + +#: src/iac_code/commands/status.py:19 +msgid "Status command requires a REPL context." +msgstr "El comando status requiere un contexto REPL." + +#: src/iac_code/commands/status.py:21 +msgid "Status is only available in interactive mode." +msgstr "status solo está disponible en modo interactivo." + +#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +msgid "Session" +msgstr "Sesión" + +#: src/iac_code/commands/status.py:31 +msgid "Provider" +msgstr "Proveedor" + +#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:33 +msgid "not configured" +msgstr "no configurado" + +#: src/iac_code/commands/status.py:32 +msgid "Model" +msgstr "Modelo" + +#: src/iac_code/commands/status.py:34 +msgid "CWD" +msgstr "Directorio actual" + +#: src/iac_code/commands/status.py:38 +msgid "API Token Usage (recorded):" +msgstr "Uso de tokens de API (registrado):" + +#: src/iac_code/commands/status.py:41 +msgid "Input" +msgstr "Entrada" + +#: src/iac_code/commands/status.py:42 +msgid "Output" +msgstr "Salida" + +#: src/iac_code/commands/status.py:43 +msgid "Cache read" +msgstr "Lectura de caché" + +#: src/iac_code/commands/status.py:44 +msgid "Total" +msgstr "Total" + +#: src/iac_code/commands/status.py:47 +msgid "No recorded API usage for this session yet." +msgstr "Aún no hay uso de API registrado para esta sesión." + +#: src/iac_code/commands/status.py:51 +msgid "Turns" +msgstr "Turnos" + +#: src/iac_code/commands/status.py:52 +msgid "Context" +msgstr "Contexto" + +#: src/iac_code/commands/status.py:54 +msgid "Session Status" +msgstr "Estado de la sesión" + +#: src/iac_code/commands/status.py:68 +#, python-brace-format +msgid "{session_id} (resumed)" +msgstr "{session_id} (reanudada)" + +#: src/iac_code/commands/status.py:76 +#, python-brace-format +msgid "{percent} used ({total} / {window})" +msgstr "{percent} usado ({total} / {window})" + # Typer/Click built-in strings #: src/iac_code/i18n/__init__.py:51 msgid "Options" @@ -1521,7 +1605,7 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "Llamando a {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:390 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Llamada correcta" @@ -1685,7 +1769,7 @@ msgstr "Importación fallida" msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:389 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "Llamada correcta (RequestId: {request_id})" @@ -1861,10 +1945,6 @@ msgstr "Su asistente de infraestructura como código con IA" msgid "Welcome back" msgstr "Bienvenido de nuevo" -#: src/iac_code/ui/banner.py:138 -msgid "Session" -msgstr "Sesión" - #: src/iac_code/ui/banner.py:148 msgid "Debug mode" msgstr "Modo depuración" @@ -1966,78 +2046,78 @@ msgstr "No, siempre denegar \"{rule}\" (esta sesión)" msgid "No, always reject this tool" msgstr "No, rechazar siempre esta herramienta" -#: src/iac_code/ui/repl.py:370 +#: src/iac_code/ui/repl.py:374 msgid "Press Ctrl+C again to exit." msgstr "Pulse Ctrl+C de nuevo para salir." -#: src/iac_code/ui/repl.py:395 +#: src/iac_code/ui/repl.py:399 msgid "Interrupted." msgstr "Interrumpido." -#: src/iac_code/ui/repl.py:432 +#: src/iac_code/ui/repl.py:436 msgid "Goodbye!" msgstr "¡Hasta luego!" -#: src/iac_code/ui/repl.py:433 +#: src/iac_code/ui/repl.py:437 msgid "Resume this session with:" msgstr "Para reanudar esta sesión, ejecute:" -#: src/iac_code/ui/repl.py:458 +#: src/iac_code/ui/repl.py:462 msgid "Update now" msgstr "Actualizar ahora" -#: src/iac_code/ui/repl.py:460 +#: src/iac_code/ui/repl.py:464 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Ejecuta el comando de actualización mostrado y sale cuando finalice " "correctamente." -#: src/iac_code/ui/repl.py:463 +#: src/iac_code/ui/repl.py:467 msgid "Skip" msgstr "Omitir" -#: src/iac_code/ui/repl.py:465 +#: src/iac_code/ui/repl.py:469 msgid "Continue with the current version for this session." msgstr "Continuar con la versión actual durante esta sesión." -#: src/iac_code/ui/repl.py:468 +#: src/iac_code/ui/repl.py:472 msgid "Skip until next version" msgstr "Omitir hasta la siguiente versión" -#: src/iac_code/ui/repl.py:470 +#: src/iac_code/ui/repl.py:474 msgid "Hide this update until a newer version is available." msgstr "" "Ocultar esta actualización hasta que haya una versión más nueva " "disponible." -#: src/iac_code/ui/repl.py:489 src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 msgid "Update command failed. Continuing with the current version." msgstr "El comando de actualización falló. Se continuará con la versión actual." -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:498 msgid "Update completed. Restart iac-code to continue." msgstr "Actualización completada. Reinicia iac-code para continuar." -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:536 msgid "No image in clipboard." msgstr "No hay ninguna imagen en el portapapeles." -#: src/iac_code/ui/repl.py:718 +#: src/iac_code/ui/repl.py:722 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:723 +#: src/iac_code/ui/repl.py:727 msgid "Shell command support is unavailable." msgstr "La compatibilidad con comandos de shell no está disponible." -#: src/iac_code/ui/repl.py:787 +#: src/iac_code/ui/repl.py:791 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Habilidad desconocida: ${name}. Escribe / para listar comandos y " "habilidades." -#: src/iac_code/ui/repl.py:789 +#: src/iac_code/ui/repl.py:793 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" @@ -2045,27 +2125,27 @@ msgstr "" "command: /{name}. Type /help for available commands.Comando desconocido: " "/{name}. Escriba /help para ver los comandos disponibles." -#: src/iac_code/ui/repl.py:794 +#: src/iac_code/ui/repl.py:798 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ solo invoca habilidades. Usa /{name} en su lugar." -#: src/iac_code/ui/repl.py:816 src/iac_code/ui/repl.py:861 +#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 #, python-brace-format msgid "Command error: {error}" msgstr "Error de comando: {error}" -#: src/iac_code/ui/repl.py:823 +#: src/iac_code/ui/repl.py:827 #, python-brace-format msgid "Command has no handler: {name}" msgstr "El comando no tiene controlador: {name}" -#: src/iac_code/ui/repl.py:1128 +#: src/iac_code/ui/repl.py:1132 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sesión no encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1147 +#: src/iac_code/ui/repl.py:1151 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2076,19 +2156,19 @@ msgstr "" "Para reanudar, ejecute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1252 msgid "This conversation is from a different directory." msgstr "Esta conversación procede de otro directorio." -#: src/iac_code/ui/repl.py:1188 +#: src/iac_code/ui/repl.py:1254 msgid "To resume, run:" msgstr "Para reanudar, ejecute:" -#: src/iac_code/ui/repl.py:1193 +#: src/iac_code/ui/repl.py:1259 msgid "(Command copied to clipboard)" msgstr "(Comando copiado al portapapeles)" -#: src/iac_code/ui/repl.py:1350 +#: src/iac_code/ui/repl.py:1416 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2097,12 +2177,12 @@ msgstr "" "El modelo actual {model} no admite entrada de imágenes. Usa /model para " "cambiar a un modelo con capacidad de visión." -#: src/iac_code/ui/repl.py:1359 +#: src/iac_code/ui/repl.py:1425 #, python-brace-format msgid "Image error: {err}" msgstr "Error de imagen: {err}" -#: src/iac_code/ui/repl.py:1376 +#: src/iac_code/ui/repl.py:1442 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2369,3 +2449,6 @@ msgstr "" #~ msgid " Option 2 - npmmirror (China-friendly mirror):" #~ msgstr " Opción 2 - npmmirror (espejo para China):" +#~ msgid "Cache create" +#~ msgstr "Creación de caché" + 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 c244520..de47e0c 100644 --- a/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.0\n" +"Project-Id-Version: iac-code 0.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 11:24+0800\n" +"POT-Creation-Date: 2026-06-02 20:13+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: fr\n" @@ -112,8 +112,8 @@ msgstr "Utilisation : /debug [on|off]" msgid "Memory manager is unavailable." msgstr "Le gestionnaire de mémoire est indisponible." -#: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 -#: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 +#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 +#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 msgid "Permission denied." msgstr "Permission refusée." @@ -611,58 +611,62 @@ msgstr "Répertoire pour les routes A2A persistées" msgid "Save the provided routes as a route snapshot" msgstr "Enregistre les routes fournies sous forme d'instantané de routes" -#: src/iac_code/commands/__init__.py:23 +#: src/iac_code/commands/__init__.py:24 msgid "Show available commands" msgstr "Afficher les commandes disponibles" -#: src/iac_code/commands/__init__.py:32 +#: src/iac_code/commands/__init__.py:33 msgid "Clear conversation history" msgstr "Effacer l’historique de conversation" -#: src/iac_code/commands/__init__.py:40 +#: src/iac_code/commands/__init__.py:41 msgid "Show or switch model" msgstr "Afficher ou changer de modèle" -#: src/iac_code/commands/__init__.py:49 +#: src/iac_code/commands/__init__.py:50 msgid "Show or switch thinking effort" msgstr "Afficher ou modifier l’effort de raisonnement" -#: src/iac_code/commands/__init__.py:58 +#: src/iac_code/commands/__init__.py:59 msgid "Compact conversation context" msgstr "Compacter le contexte de conversation" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compacting conversation" msgstr "Compactage de la conversation" -#: src/iac_code/commands/__init__.py:67 +#: src/iac_code/commands/__init__.py:68 msgid "Exit the application" msgstr "Quitter l’application" -#: src/iac_code/commands/__init__.py:76 +#: src/iac_code/commands/__init__.py:77 msgid "Authenticate with LLM provider" msgstr "S’authentifier auprès du fournisseur LLM" -#: src/iac_code/commands/__init__.py:85 +#: src/iac_code/commands/__init__.py:86 msgid "Toggle debug logging" msgstr "Activer ou désactiver la journalisation debug" -#: src/iac_code/commands/__init__.py:94 +#: src/iac_code/commands/__init__.py:95 msgid "View and manage persistent memories" msgstr "Afficher et gérer les mémoires persistantes" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:103 +#: src/iac_code/commands/__init__.py:104 msgid "Resume a previous session" msgstr "Reprendre une session précédente" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "[conversation id or search term]" msgstr "[identifiant de conversation ou terme de recherche]" +#: src/iac_code/commands/__init__.py:113 +msgid "Show current session status" +msgstr "Afficher l’état actuel de la session" + #: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" @@ -842,7 +846,8 @@ msgid "Credential" msgstr "Identifiants" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Région" @@ -1055,6 +1060,85 @@ msgstr "Session introuvable : {arg}" msgid "Resume cancelled" msgstr "Reprise annulée" +#: src/iac_code/commands/status.py:16 +msgid "Status command requires a context." +msgstr "La commande status nécessite un contexte." + +#: src/iac_code/commands/status.py:19 +msgid "Status command requires a REPL context." +msgstr "La commande status nécessite un contexte REPL." + +#: src/iac_code/commands/status.py:21 +msgid "Status is only available in interactive mode." +msgstr "status n’est disponible qu’en mode interactif." + +#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +msgid "Session" +msgstr "Session" + +#: src/iac_code/commands/status.py:31 +msgid "Provider" +msgstr "Fournisseur" + +#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:33 +msgid "not configured" +msgstr "non configuré" + +#: src/iac_code/commands/status.py:32 +msgid "Model" +msgstr "Modèle" + +#: src/iac_code/commands/status.py:34 +msgid "CWD" +msgstr "Répertoire courant" + +#: src/iac_code/commands/status.py:38 +msgid "API Token Usage (recorded):" +msgstr "Utilisation des tokens API (enregistrée) :" + +#: src/iac_code/commands/status.py:41 +msgid "Input" +msgstr "Entrée" + +#: src/iac_code/commands/status.py:42 +msgid "Output" +msgstr "Sortie" + +#: src/iac_code/commands/status.py:43 +msgid "Cache read" +msgstr "Lecture du cache" + +#: src/iac_code/commands/status.py:44 +msgid "Total" +msgstr "Total" + +#: src/iac_code/commands/status.py:47 +msgid "No recorded API usage for this session yet." +msgstr "Aucune utilisation d’API enregistrée pour cette session pour le moment." + +#: src/iac_code/commands/status.py:51 +msgid "Turns" +msgstr "Tours" + +#: src/iac_code/commands/status.py:52 +msgid "Context" +msgstr "Contexte" + +#: src/iac_code/commands/status.py:54 +msgid "Session Status" +msgstr "État de la session" + +#: src/iac_code/commands/status.py:68 +#, python-brace-format +msgid "{session_id} (resumed)" +msgstr "{session_id} (reprise)" + +#: src/iac_code/commands/status.py:76 +#, python-brace-format +msgid "{percent} used ({total} / {window})" +msgstr "{percent} utilisé ({total} / {window})" + # Typer/Click built-in strings #: src/iac_code/i18n/__init__.py:51 msgid "Options" @@ -1524,7 +1608,7 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "Appel de {action}…" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:390 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Appel réussi" @@ -1688,7 +1772,7 @@ msgstr "ÉCHEC DE L’IMPORT" msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:389 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "Appel réussi (RequestId : {request_id})" @@ -1863,10 +1947,6 @@ msgstr "Votre assistant Infrastructure as Code assisté par IA" msgid "Welcome back" msgstr "Bon retour" -#: src/iac_code/ui/banner.py:138 -msgid "Session" -msgstr "Session" - #: src/iac_code/ui/banner.py:148 msgid "Debug mode" msgstr "Mode debug" @@ -1970,103 +2050,103 @@ msgstr "Non, toujours refuser \"{rule}\" (cette session)" msgid "No, always reject this tool" msgstr "Non, toujours refuser cet outil" -#: src/iac_code/ui/repl.py:370 +#: src/iac_code/ui/repl.py:374 msgid "Press Ctrl+C again to exit." msgstr "Appuyez de nouveau sur Ctrl+C pour quitter." -#: src/iac_code/ui/repl.py:395 +#: src/iac_code/ui/repl.py:399 msgid "Interrupted." msgstr "Interrompu." -#: src/iac_code/ui/repl.py:432 +#: src/iac_code/ui/repl.py:436 msgid "Goodbye!" msgstr "Au revoir !" -#: src/iac_code/ui/repl.py:433 +#: src/iac_code/ui/repl.py:437 msgid "Resume this session with:" msgstr "Pour reprendre cette session :" -#: src/iac_code/ui/repl.py:458 +#: src/iac_code/ui/repl.py:462 msgid "Update now" msgstr "Mettre à jour maintenant" -#: src/iac_code/ui/repl.py:460 +#: src/iac_code/ui/repl.py:464 msgid "Run the shown update command and exit when it succeeds." msgstr "Exécute la commande de mise à jour affichée et quitte en cas de succès." -#: src/iac_code/ui/repl.py:463 +#: src/iac_code/ui/repl.py:467 msgid "Skip" msgstr "Ignorer" -#: src/iac_code/ui/repl.py:465 +#: src/iac_code/ui/repl.py:469 msgid "Continue with the current version for this session." msgstr "Continuer avec la version actuelle pour cette session." -#: src/iac_code/ui/repl.py:468 +#: src/iac_code/ui/repl.py:472 msgid "Skip until next version" msgstr "Ignorer jusqu’à la prochaine version" -#: src/iac_code/ui/repl.py:470 +#: src/iac_code/ui/repl.py:474 msgid "Hide this update until a newer version is available." msgstr "" "Masquer cette mise à jour jusqu’à ce qu’une version plus récente soit " "disponible." -#: src/iac_code/ui/repl.py:489 src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 msgid "Update command failed. Continuing with the current version." msgstr "La commande de mise à jour a échoué. La version actuelle sera conservée." -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:498 msgid "Update completed. Restart iac-code to continue." msgstr "Mise à jour terminée. Redémarrez iac-code pour continuer." -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:536 msgid "No image in clipboard." msgstr "Aucune image dans le presse-papiers." -#: src/iac_code/ui/repl.py:718 +#: src/iac_code/ui/repl.py:722 msgid "Usage: !" msgstr "Utilisation : !" -#: src/iac_code/ui/repl.py:723 +#: src/iac_code/ui/repl.py:727 msgid "Shell command support is unavailable." msgstr "La prise en charge des commandes shell n'est pas disponible." -#: src/iac_code/ui/repl.py:787 +#: src/iac_code/ui/repl.py:791 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Compétence inconnue : ${name}. Tapez / pour lister les commandes et les " "compétences." -#: src/iac_code/ui/repl.py:789 +#: src/iac_code/ui/repl.py:793 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available commands.Commande " "inconnue : /{name}. Saisissez /help pour la liste des commandes." -#: src/iac_code/ui/repl.py:794 +#: src/iac_code/ui/repl.py:798 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ n'invoque que des compétences. Utilisez plutôt /{name}." -#: src/iac_code/ui/repl.py:816 src/iac_code/ui/repl.py:861 +#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 #, python-brace-format msgid "Command error: {error}" msgstr "Erreur de commande : {error}" -#: src/iac_code/ui/repl.py:823 +#: src/iac_code/ui/repl.py:827 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Aucun gestionnaire pour la commande : {name}" -#: src/iac_code/ui/repl.py:1128 +#: src/iac_code/ui/repl.py:1132 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Session introuvable : {session_id}" -#: src/iac_code/ui/repl.py:1147 +#: src/iac_code/ui/repl.py:1151 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2077,19 +2157,19 @@ msgstr "" "Pour la reprendre, exécutez :\n" " {cmd}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1252 msgid "This conversation is from a different directory." msgstr "Cette conversation provient d’un autre répertoire." -#: src/iac_code/ui/repl.py:1188 +#: src/iac_code/ui/repl.py:1254 msgid "To resume, run:" msgstr "Pour reprendre, exécutez :" -#: src/iac_code/ui/repl.py:1193 +#: src/iac_code/ui/repl.py:1259 msgid "(Command copied to clipboard)" msgstr "(Commande copiée dans le presse-papiers)" -#: src/iac_code/ui/repl.py:1350 +#: src/iac_code/ui/repl.py:1416 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2098,12 +2178,12 @@ msgstr "" "Le modèle actuel {model} ne prend pas en charge l’entrée d’image. " "Utilisez /model pour passer à un modèle compatible vision." -#: src/iac_code/ui/repl.py:1359 +#: src/iac_code/ui/repl.py:1425 #, python-brace-format msgid "Image error: {err}" msgstr "Erreur d’image : {err}" -#: src/iac_code/ui/repl.py:1376 +#: src/iac_code/ui/repl.py:1442 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2355,3 +2435,6 @@ msgstr "" #~ msgid " Option 2 - npmmirror (China-friendly mirror):" #~ msgstr " Option 2 - npmmirror (miroir pour la Chine) :" +#~ msgid "Cache create" +#~ msgstr "Création du cache" + 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 9f7c8d7..0a7da05 100644 --- a/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.0\n" +"Project-Id-Version: iac-code 0.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 11:24+0800\n" +"POT-Creation-Date: 2026-06-02 20:13+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: ja\n" @@ -106,8 +106,8 @@ msgstr "使用方法:/debug [on|off]" msgid "Memory manager is unavailable." msgstr "メモリマネージャーを利用できません。" -#: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 -#: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 +#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 +#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 msgid "Permission denied." msgstr "権限が拒否されました。" @@ -587,58 +587,62 @@ msgstr "永続化された A2A ルート用のディレクトリ" msgid "Save the provided routes as a route snapshot" msgstr "指定されたルートをルートスナップショットとして保存します" -#: src/iac_code/commands/__init__.py:23 +#: src/iac_code/commands/__init__.py:24 msgid "Show available commands" msgstr "利用可能なコマンドを表示します" -#: src/iac_code/commands/__init__.py:32 +#: src/iac_code/commands/__init__.py:33 msgid "Clear conversation history" msgstr "会話履歴をクリアします" -#: src/iac_code/commands/__init__.py:40 +#: src/iac_code/commands/__init__.py:41 msgid "Show or switch model" msgstr "モデルを表示または切り替えます" -#: src/iac_code/commands/__init__.py:49 +#: src/iac_code/commands/__init__.py:50 msgid "Show or switch thinking effort" msgstr "思考の負荷(effort)を表示または切り替えます" -#: src/iac_code/commands/__init__.py:58 +#: src/iac_code/commands/__init__.py:59 msgid "Compact conversation context" msgstr "会話コンテキストを圧縮します" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compacting conversation" msgstr "会話を圧縮しています" -#: src/iac_code/commands/__init__.py:67 +#: src/iac_code/commands/__init__.py:68 msgid "Exit the application" msgstr "アプリケーションを終了します" -#: src/iac_code/commands/__init__.py:76 +#: src/iac_code/commands/__init__.py:77 msgid "Authenticate with LLM provider" msgstr "LLM プロバイダーで認証を行います" -#: src/iac_code/commands/__init__.py:85 +#: src/iac_code/commands/__init__.py:86 msgid "Toggle debug logging" msgstr "デバッグログのオン/オフを切り替えます" -#: src/iac_code/commands/__init__.py:94 +#: src/iac_code/commands/__init__.py:95 msgid "View and manage persistent memories" msgstr "永続メモリを表示および管理" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "[|search |delete |help]" msgstr "[<名前>|search <検索語>|delete <名前>|help]" -#: src/iac_code/commands/__init__.py:103 +#: src/iac_code/commands/__init__.py:104 msgid "Resume a previous session" msgstr "以前のセッションを再開します" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "[conversation id or search term]" msgstr "[会話 ID または検索語]" +#: src/iac_code/commands/__init__.py:113 +msgid "Show current session status" +msgstr "現在のセッション状態を表示" + #: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" @@ -818,7 +822,8 @@ msgid "Credential" msgstr "クレデンシャル" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "リージョン" @@ -1027,6 +1032,85 @@ msgstr "セッションが見つかりません:{arg}" msgid "Resume cancelled" msgstr "resume をキャンセルしました" +#: src/iac_code/commands/status.py:16 +msgid "Status command requires a context." +msgstr "status コマンドにはコンテキストが必要です。" + +#: src/iac_code/commands/status.py:19 +msgid "Status command requires a REPL context." +msgstr "status コマンドには REPL コンテキストが必要です。" + +#: src/iac_code/commands/status.py:21 +msgid "Status is only available in interactive mode." +msgstr "status は対話モードでのみ利用できます。" + +#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +msgid "Session" +msgstr "セッション" + +#: src/iac_code/commands/status.py:31 +msgid "Provider" +msgstr "プロバイダー" + +#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:33 +msgid "not configured" +msgstr "未設定" + +#: src/iac_code/commands/status.py:32 +msgid "Model" +msgstr "モデル" + +#: src/iac_code/commands/status.py:34 +msgid "CWD" +msgstr "現在のディレクトリ" + +#: src/iac_code/commands/status.py:38 +msgid "API Token Usage (recorded):" +msgstr "API トークン使用量(記録済み):" + +#: src/iac_code/commands/status.py:41 +msgid "Input" +msgstr "入力" + +#: src/iac_code/commands/status.py:42 +msgid "Output" +msgstr "出力" + +#: src/iac_code/commands/status.py:43 +msgid "Cache read" +msgstr "キャッシュ読み取り" + +#: src/iac_code/commands/status.py:44 +msgid "Total" +msgstr "合計" + +#: src/iac_code/commands/status.py:47 +msgid "No recorded API usage for this session yet." +msgstr "このセッションにはまだ記録済みの API 使用量がありません。" + +#: src/iac_code/commands/status.py:51 +msgid "Turns" +msgstr "ターン" + +#: src/iac_code/commands/status.py:52 +msgid "Context" +msgstr "コンテキスト" + +#: src/iac_code/commands/status.py:54 +msgid "Session Status" +msgstr "セッション状態" + +#: src/iac_code/commands/status.py:68 +#, python-brace-format +msgid "{session_id} (resumed)" +msgstr "{session_id}(再開済み)" + +#: src/iac_code/commands/status.py:76 +#, python-brace-format +msgid "{percent} used ({total} / {window})" +msgstr "{percent} 使用済み({total} / {window})" + # Typer/Click built-in strings #: src/iac_code/i18n/__init__.py:51 msgid "Options" @@ -1486,7 +1570,7 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "{action} を呼び出しています…" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:390 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "呼び出しに成功しました" @@ -1650,7 +1734,7 @@ msgstr "インポート失敗" msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:389 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "呼び出しに成功しました(RequestId: {request_id})" @@ -1810,10 +1894,6 @@ msgstr "あなたの AI 駆動の Infrastructure as Code アシスタントで msgid "Welcome back" msgstr "おかえりなさい" -#: src/iac_code/ui/banner.py:138 -msgid "Session" -msgstr "セッション" - #: src/iac_code/ui/banner.py:148 msgid "Debug mode" msgstr "デバッグモード" @@ -1915,99 +1995,99 @@ msgstr "いいえ、常に \"{rule}\" を拒否(このセッション)" msgid "No, always reject this tool" msgstr "いいえ、このツールは常に拒否" -#: src/iac_code/ui/repl.py:370 +#: src/iac_code/ui/repl.py:374 msgid "Press Ctrl+C again to exit." msgstr "終了するには Ctrl+C をもう一度押してください。" -#: src/iac_code/ui/repl.py:395 +#: src/iac_code/ui/repl.py:399 msgid "Interrupted." msgstr "中断しました。" -#: src/iac_code/ui/repl.py:432 +#: src/iac_code/ui/repl.py:436 msgid "Goodbye!" msgstr "さようなら。" -#: src/iac_code/ui/repl.py:433 +#: src/iac_code/ui/repl.py:437 msgid "Resume this session with:" msgstr "このセッションを再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:458 +#: src/iac_code/ui/repl.py:462 msgid "Update now" msgstr "今すぐ更新" -#: src/iac_code/ui/repl.py:460 +#: src/iac_code/ui/repl.py:464 msgid "Run the shown update command and exit when it succeeds." msgstr "表示された更新コマンドを実行し、成功したら終了します。" -#: src/iac_code/ui/repl.py:463 +#: src/iac_code/ui/repl.py:467 msgid "Skip" msgstr "スキップ" -#: src/iac_code/ui/repl.py:465 +#: src/iac_code/ui/repl.py:469 msgid "Continue with the current version for this session." msgstr "このセッションでは現在のバージョンを使い続けます。" -#: src/iac_code/ui/repl.py:468 +#: src/iac_code/ui/repl.py:472 msgid "Skip until next version" msgstr "次のバージョンまでスキップ" -#: src/iac_code/ui/repl.py:470 +#: src/iac_code/ui/repl.py:474 msgid "Hide this update until a newer version is available." msgstr "より新しいバージョンが利用可能になるまで、この更新を非表示にします。" -#: src/iac_code/ui/repl.py:489 src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 msgid "Update command failed. Continuing with the current version." msgstr "更新コマンドに失敗しました。現在のバージョンで続行します。" -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:498 msgid "Update completed. Restart iac-code to continue." msgstr "更新が完了しました。続行するには iac-code を再起動してください。" -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:536 msgid "No image in clipboard." msgstr "クリップボードに画像がありません。" -#: src/iac_code/ui/repl.py:718 +#: src/iac_code/ui/repl.py:722 msgid "Usage: !" msgstr "使用方法: !" -#: src/iac_code/ui/repl.py:723 +#: src/iac_code/ui/repl.py:727 msgid "Shell command support is unavailable." msgstr "シェルコマンドのサポートは利用できません。" -#: src/iac_code/ui/repl.py:787 +#: src/iac_code/ui/repl.py:791 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "不明なスキル: ${name}。/ を入力するとコマンドとスキルを一覧表示します。" -#: src/iac_code/ui/repl.py:789 +#: src/iac_code/ui/repl.py:793 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available " "commands.不明なコマンドです:/{name}。利用可能なコマンドは /help を入力してください。" -#: src/iac_code/ui/repl.py:794 +#: src/iac_code/ui/repl.py:798 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ はスキルのみを呼び出します。代わりに /{name} を使用してください。" -#: src/iac_code/ui/repl.py:816 src/iac_code/ui/repl.py:861 +#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 #, python-brace-format msgid "Command error: {error}" msgstr "コマンドエラー:{error}" -#: src/iac_code/ui/repl.py:823 +#: src/iac_code/ui/repl.py:827 #, python-brace-format msgid "Command has no handler: {name}" msgstr "ハンドラーがないコマンドです:{name}" -#: src/iac_code/ui/repl.py:1128 +#: src/iac_code/ui/repl.py:1132 #, python-brace-format msgid "Session not found: {session_id}" msgstr "セッションが見つかりません:{session_id}" -#: src/iac_code/ui/repl.py:1147 +#: src/iac_code/ui/repl.py:1151 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2018,31 +2098,31 @@ msgstr "" "再開するには次を実行してください:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1252 msgid "This conversation is from a different directory." msgstr "この会話は別のディレクトリ由来です。" -#: src/iac_code/ui/repl.py:1188 +#: src/iac_code/ui/repl.py:1254 msgid "To resume, run:" msgstr "再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:1193 +#: src/iac_code/ui/repl.py:1259 msgid "(Command copied to clipboard)" msgstr "(コマンドをクリップボードにコピーしました)" -#: src/iac_code/ui/repl.py:1350 +#: src/iac_code/ui/repl.py:1416 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " "to a vision-capable model." msgstr "現在のモデル {model} は画像入力をサポートしていません。/model を使用してビジョン対応モデルに切り替えてください。" -#: src/iac_code/ui/repl.py:1359 +#: src/iac_code/ui/repl.py:1425 #, python-brace-format msgid "Image error: {err}" msgstr "画像エラー:{err}" -#: src/iac_code/ui/repl.py:1376 +#: src/iac_code/ui/repl.py:1442 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2302,3 +2382,6 @@ msgstr " オプション 2 - github.com にアクセスできない場合は、 #~ msgid " Option 2 - npmmirror (China-friendly mirror):" #~ msgstr " 方法 2 - npmmirror(中国向けミラー):" +#~ msgid "Cache create" +#~ 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 7008b3b..29be64a 100644 --- a/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.0\n" +"Project-Id-Version: iac-code 0.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 11:24+0800\n" +"POT-Creation-Date: 2026-06-02 20:13+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: pt\n" @@ -112,8 +112,8 @@ msgstr "Uso: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "O gerenciador de memória está indisponível." -#: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 -#: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 +#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 +#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 msgid "Permission denied." msgstr "Permissão negada." @@ -607,58 +607,62 @@ msgstr "Diretório para rotas A2A persistidas" msgid "Save the provided routes as a route snapshot" msgstr "Salva as rotas fornecidas como um snapshot de rotas" -#: src/iac_code/commands/__init__.py:23 +#: src/iac_code/commands/__init__.py:24 msgid "Show available commands" msgstr "Mostrar comandos disponíveis" -#: src/iac_code/commands/__init__.py:32 +#: src/iac_code/commands/__init__.py:33 msgid "Clear conversation history" msgstr "Limpar histórico da conversa" -#: src/iac_code/commands/__init__.py:40 +#: src/iac_code/commands/__init__.py:41 msgid "Show or switch model" msgstr "Exibir ou trocar modelo" -#: src/iac_code/commands/__init__.py:49 +#: src/iac_code/commands/__init__.py:50 msgid "Show or switch thinking effort" msgstr "Exibir ou alterar o nível de esforço de raciocínio" -#: src/iac_code/commands/__init__.py:58 +#: src/iac_code/commands/__init__.py:59 msgid "Compact conversation context" msgstr "Compactar contexto da conversa" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compacting conversation" msgstr "Compactando conversa" -#: src/iac_code/commands/__init__.py:67 +#: src/iac_code/commands/__init__.py:68 msgid "Exit the application" msgstr "Encerrar o aplicativo" -#: src/iac_code/commands/__init__.py:76 +#: src/iac_code/commands/__init__.py:77 msgid "Authenticate with LLM provider" msgstr "Autenticar com o provedor LLM" -#: src/iac_code/commands/__init__.py:85 +#: src/iac_code/commands/__init__.py:86 msgid "Toggle debug logging" msgstr "Alternar debug" -#: src/iac_code/commands/__init__.py:94 +#: src/iac_code/commands/__init__.py:95 msgid "View and manage persistent memories" msgstr "Ver e gerenciar memórias persistentes" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:103 +#: src/iac_code/commands/__init__.py:104 msgid "Resume a previous session" msgstr "Retomar uma sessão anterior" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "[conversation id or search term]" msgstr "[ID da conversa ou termo de busca]" +#: src/iac_code/commands/__init__.py:113 +msgid "Show current session status" +msgstr "Mostrar o status atual da sessão" + #: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" @@ -838,7 +842,8 @@ msgid "Credential" msgstr "Credencial" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Região" @@ -1045,6 +1050,85 @@ msgstr "Sessão não encontrada: {arg}" msgid "Resume cancelled" msgstr "Retomada cancelada" +#: src/iac_code/commands/status.py:16 +msgid "Status command requires a context." +msgstr "O comando status requer um contexto." + +#: src/iac_code/commands/status.py:19 +msgid "Status command requires a REPL context." +msgstr "O comando status requer um contexto REPL." + +#: src/iac_code/commands/status.py:21 +msgid "Status is only available in interactive mode." +msgstr "status está disponível apenas no modo interativo." + +#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +msgid "Session" +msgstr "Sessão" + +#: src/iac_code/commands/status.py:31 +msgid "Provider" +msgstr "Provedor" + +#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:33 +msgid "not configured" +msgstr "não configurado" + +#: src/iac_code/commands/status.py:32 +msgid "Model" +msgstr "Modelo" + +#: src/iac_code/commands/status.py:34 +msgid "CWD" +msgstr "Diretório atual" + +#: src/iac_code/commands/status.py:38 +msgid "API Token Usage (recorded):" +msgstr "Uso de tokens da API (registrado):" + +#: src/iac_code/commands/status.py:41 +msgid "Input" +msgstr "Entrada" + +#: src/iac_code/commands/status.py:42 +msgid "Output" +msgstr "Saída" + +#: src/iac_code/commands/status.py:43 +msgid "Cache read" +msgstr "Leitura de cache" + +#: src/iac_code/commands/status.py:44 +msgid "Total" +msgstr "Total" + +#: src/iac_code/commands/status.py:47 +msgid "No recorded API usage for this session yet." +msgstr "Ainda não há uso de API registrado para esta sessão." + +#: src/iac_code/commands/status.py:51 +msgid "Turns" +msgstr "Turnos" + +#: src/iac_code/commands/status.py:52 +msgid "Context" +msgstr "Contexto" + +#: src/iac_code/commands/status.py:54 +msgid "Session Status" +msgstr "Status da sessão" + +#: src/iac_code/commands/status.py:68 +#, python-brace-format +msgid "{session_id} (resumed)" +msgstr "{session_id} (retomada)" + +#: src/iac_code/commands/status.py:76 +#, python-brace-format +msgid "{percent} used ({total} / {window})" +msgstr "{percent} usado ({total} / {window})" + # Typer/Click built-in strings #: src/iac_code/i18n/__init__.py:51 msgid "Options" @@ -1512,7 +1596,7 @@ msgstr "API na nuvem" msgid "Calling {action}..." msgstr "Chamando {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:390 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Chamada bem-sucedida" @@ -1676,7 +1760,7 @@ msgstr "Falha na importação" msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:389 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "Chamada bem-sucedida (RequestId: {request_id})" @@ -1846,10 +1930,6 @@ msgstr "Seu assistente de Infrastructure as Code com IA" msgid "Welcome back" msgstr "Bem-vindo de volta" -#: src/iac_code/ui/banner.py:138 -msgid "Session" -msgstr "Sessão" - #: src/iac_code/ui/banner.py:148 msgid "Debug mode" msgstr "Modo debug" @@ -1951,101 +2031,101 @@ msgstr "Não, sempre negar \"{rule}\" (esta sessão)" msgid "No, always reject this tool" msgstr "Não, sempre rejeitar esta ferramenta" -#: src/iac_code/ui/repl.py:370 +#: src/iac_code/ui/repl.py:374 msgid "Press Ctrl+C again to exit." msgstr "Pressione Ctrl+C novamente para sair." -#: src/iac_code/ui/repl.py:395 +#: src/iac_code/ui/repl.py:399 msgid "Interrupted." msgstr "Interrompido." -#: src/iac_code/ui/repl.py:432 +#: src/iac_code/ui/repl.py:436 msgid "Goodbye!" msgstr "Até logo!" -#: src/iac_code/ui/repl.py:433 +#: src/iac_code/ui/repl.py:437 msgid "Resume this session with:" msgstr "Para retomar esta sessão, execute:" -#: src/iac_code/ui/repl.py:458 +#: src/iac_code/ui/repl.py:462 msgid "Update now" msgstr "Atualizar agora" -#: src/iac_code/ui/repl.py:460 +#: src/iac_code/ui/repl.py:464 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Executa o comando de atualização mostrado e sai quando ele for concluído " "com sucesso." -#: src/iac_code/ui/repl.py:463 +#: src/iac_code/ui/repl.py:467 msgid "Skip" msgstr "Ignorar" -#: src/iac_code/ui/repl.py:465 +#: src/iac_code/ui/repl.py:469 msgid "Continue with the current version for this session." msgstr "Continuar com a versão atual nesta sessão." -#: src/iac_code/ui/repl.py:468 +#: src/iac_code/ui/repl.py:472 msgid "Skip until next version" msgstr "Ignorar até a próxima versão" -#: src/iac_code/ui/repl.py:470 +#: src/iac_code/ui/repl.py:474 msgid "Hide this update until a newer version is available." msgstr "Ocultar esta atualização até que uma versão mais nova esteja disponível." -#: src/iac_code/ui/repl.py:489 src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 msgid "Update command failed. Continuing with the current version." msgstr "O comando de atualização falhou. Continuando com a versão atual." -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:498 msgid "Update completed. Restart iac-code to continue." msgstr "Atualização concluída. Reinicie o iac-code para continuar." -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:536 msgid "No image in clipboard." msgstr "Nenhuma imagem na área de transferência." -#: src/iac_code/ui/repl.py:718 +#: src/iac_code/ui/repl.py:722 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:723 +#: src/iac_code/ui/repl.py:727 msgid "Shell command support is unavailable." msgstr "O suporte a comandos shell não está disponível." -#: src/iac_code/ui/repl.py:787 +#: src/iac_code/ui/repl.py:791 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Habilidade desconhecida: ${name}. Digite / para listar comandos e " "habilidades." -#: src/iac_code/ui/repl.py:789 +#: src/iac_code/ui/repl.py:793 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "Comando desconhecido: /{name}. Digite /help para ver os comandos." -#: src/iac_code/ui/repl.py:794 +#: src/iac_code/ui/repl.py:798 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ invoca apenas habilidades. Use /{name} em vez disso." -#: src/iac_code/ui/repl.py:816 src/iac_code/ui/repl.py:861 +#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 #, python-brace-format msgid "Command error: {error}" msgstr "Erro de comando: {error}" -#: src/iac_code/ui/repl.py:823 +#: src/iac_code/ui/repl.py:827 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Comando sem tratador: {name}" -#: src/iac_code/ui/repl.py:1128 +#: src/iac_code/ui/repl.py:1132 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sessão não encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1147 +#: src/iac_code/ui/repl.py:1151 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2056,19 +2136,19 @@ msgstr "" "Para retomar, execute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1252 msgid "This conversation is from a different directory." msgstr "Esta conversa é de outro diretório." -#: src/iac_code/ui/repl.py:1188 +#: src/iac_code/ui/repl.py:1254 msgid "To resume, run:" msgstr "Para retomar, execute:" -#: src/iac_code/ui/repl.py:1193 +#: src/iac_code/ui/repl.py:1259 msgid "(Command copied to clipboard)" msgstr "(Comando copiado para a área de transferência)" -#: src/iac_code/ui/repl.py:1350 +#: src/iac_code/ui/repl.py:1416 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2077,12 +2157,12 @@ msgstr "" "O modelo atual {model} não suporta entrada de imagem. Use /model para " "alternar para um modelo com capacidade de visão." -#: src/iac_code/ui/repl.py:1359 +#: src/iac_code/ui/repl.py:1425 #, python-brace-format msgid "Image error: {err}" msgstr "Erro de imagem: {err}" -#: src/iac_code/ui/repl.py:1376 +#: src/iac_code/ui/repl.py:1442 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2334,3 +2414,6 @@ msgstr "" #~ msgid " Option 2 - npmmirror (China-friendly mirror):" #~ msgstr " Opção 2 - npmmirror (espelho para a China):" +#~ msgid "Cache create" +#~ msgstr "Criação de cache" + 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 a178f94..93b7336 100644 --- a/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.0\n" +"Project-Id-Version: iac-code 0.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 11:24+0800\n" +"POT-Creation-Date: 2026-06-02 20:13+0800\n" "PO-Revision-Date: 2026-04-02 00:00+0000\n" "Last-Translator: \n" "Language: zh\n" @@ -102,8 +102,8 @@ msgstr "用法:/debug [on|off]" msgid "Memory manager is unavailable." msgstr "记忆管理器不可用。" -#: src/iac_code/agent/agent_loop.py:404 src/iac_code/agent/agent_loop.py:419 -#: src/iac_code/ui/repl.py:757 src/iac_code/ui/repl.py:771 +#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 +#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 msgid "Permission denied." msgstr "权限被拒绝。" @@ -579,58 +579,62 @@ msgstr "持久化 A2A 路由的目录" msgid "Save the provided routes as a route snapshot" msgstr "将提供的路由保存为路由快照" -#: src/iac_code/commands/__init__.py:23 +#: src/iac_code/commands/__init__.py:24 msgid "Show available commands" msgstr "显示可用命令" -#: src/iac_code/commands/__init__.py:32 +#: src/iac_code/commands/__init__.py:33 msgid "Clear conversation history" msgstr "清除对话历史" -#: src/iac_code/commands/__init__.py:40 +#: src/iac_code/commands/__init__.py:41 msgid "Show or switch model" msgstr "显示或切换模型" -#: src/iac_code/commands/__init__.py:49 +#: src/iac_code/commands/__init__.py:50 msgid "Show or switch thinking effort" msgstr "显示或切换思考强度" -#: src/iac_code/commands/__init__.py:58 +#: src/iac_code/commands/__init__.py:59 msgid "Compact conversation context" msgstr "压缩对话上下文" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compacting conversation" msgstr "正在压缩对话" -#: src/iac_code/commands/__init__.py:67 +#: src/iac_code/commands/__init__.py:68 msgid "Exit the application" msgstr "退出应用程序" -#: src/iac_code/commands/__init__.py:76 +#: src/iac_code/commands/__init__.py:77 msgid "Authenticate with LLM provider" msgstr "配置 LLM 提供商认证" -#: src/iac_code/commands/__init__.py:85 +#: src/iac_code/commands/__init__.py:86 msgid "Toggle debug logging" msgstr "切换调试日志开关" -#: src/iac_code/commands/__init__.py:94 +#: src/iac_code/commands/__init__.py:95 msgid "View and manage persistent memories" msgstr "查看和管理持久记忆" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "[|search |delete |help]" msgstr "[<名称>|search <查询>|delete <名称>|help]" -#: src/iac_code/commands/__init__.py:103 +#: src/iac_code/commands/__init__.py:104 msgid "Resume a previous session" msgstr "恢复之前的会话" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "[conversation id or search term]" msgstr "[会话 ID 或搜索词]" +#: src/iac_code/commands/__init__.py:113 +msgid "Show current session status" +msgstr "显示当前会话状态" + #: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" @@ -810,7 +814,8 @@ msgid "Credential" msgstr "凭证" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "地域" @@ -1015,6 +1020,85 @@ msgstr "未找到会话:{arg}" msgid "Resume cancelled" msgstr "已取消恢复" +#: src/iac_code/commands/status.py:16 +msgid "Status command requires a context." +msgstr "status 命令需要上下文。" + +#: src/iac_code/commands/status.py:19 +msgid "Status command requires a REPL context." +msgstr "status 命令需要 REPL 上下文。" + +#: src/iac_code/commands/status.py:21 +msgid "Status is only available in interactive mode." +msgstr "status 仅在交互模式下可用。" + +#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +msgid "Session" +msgstr "会话" + +#: src/iac_code/commands/status.py:31 +msgid "Provider" +msgstr "提供商" + +#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:33 +msgid "not configured" +msgstr "未配置" + +#: src/iac_code/commands/status.py:32 +msgid "Model" +msgstr "模型" + +#: src/iac_code/commands/status.py:34 +msgid "CWD" +msgstr "当前目录" + +#: src/iac_code/commands/status.py:38 +msgid "API Token Usage (recorded):" +msgstr "API Token 用量(已记录):" + +#: src/iac_code/commands/status.py:41 +msgid "Input" +msgstr "输入" + +#: src/iac_code/commands/status.py:42 +msgid "Output" +msgstr "输出" + +#: src/iac_code/commands/status.py:43 +msgid "Cache read" +msgstr "缓存读取" + +#: src/iac_code/commands/status.py:44 +msgid "Total" +msgstr "总计" + +#: src/iac_code/commands/status.py:47 +msgid "No recorded API usage for this session yet." +msgstr "此会话尚无已记录的 API 用量。" + +#: src/iac_code/commands/status.py:51 +msgid "Turns" +msgstr "轮次" + +#: src/iac_code/commands/status.py:52 +msgid "Context" +msgstr "上下文" + +#: src/iac_code/commands/status.py:54 +msgid "Session Status" +msgstr "会话状态" + +#: src/iac_code/commands/status.py:68 +#, python-brace-format +msgid "{session_id} (resumed)" +msgstr "{session_id}(已恢复)" + +#: src/iac_code/commands/status.py:76 +#, python-brace-format +msgid "{percent} used ({total} / {window})" +msgstr "已使用 {percent}({total} / {window})" + # Typer/Click built-in strings #: src/iac_code/i18n/__init__.py:51 msgid "Options" @@ -1469,7 +1553,7 @@ msgstr "云API" msgid "Calling {action}..." msgstr "正在调用 {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:390 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "调用成功" @@ -1633,7 +1717,7 @@ msgstr "导入失败" msgid "Aliyun API" msgstr "阿里云 API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:389 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "调用成功(RequestId: {request_id})" @@ -1791,10 +1875,6 @@ msgstr "您的 AI 驱动的基础设施即代码助手" msgid "Welcome back" msgstr "欢迎回来" -#: src/iac_code/ui/banner.py:138 -msgid "Session" -msgstr "会话" - #: src/iac_code/ui/banner.py:148 msgid "Debug mode" msgstr "调试模式" @@ -1896,97 +1976,97 @@ msgstr "否,始终拒绝 \"{rule}\"(本次会话)" msgid "No, always reject this tool" msgstr "否,始终拒绝此工具" -#: src/iac_code/ui/repl.py:370 +#: src/iac_code/ui/repl.py:374 msgid "Press Ctrl+C again to exit." msgstr "再次按 Ctrl+C 退出。" -#: src/iac_code/ui/repl.py:395 +#: src/iac_code/ui/repl.py:399 msgid "Interrupted." msgstr "已中断。" -#: src/iac_code/ui/repl.py:432 +#: src/iac_code/ui/repl.py:436 msgid "Goodbye!" msgstr "再见!" -#: src/iac_code/ui/repl.py:433 +#: src/iac_code/ui/repl.py:437 msgid "Resume this session with:" msgstr "恢复此会话请运行:" -#: src/iac_code/ui/repl.py:458 +#: src/iac_code/ui/repl.py:462 msgid "Update now" msgstr "立即更新" -#: src/iac_code/ui/repl.py:460 +#: src/iac_code/ui/repl.py:464 msgid "Run the shown update command and exit when it succeeds." msgstr "运行显示的更新命令,成功后退出。" -#: src/iac_code/ui/repl.py:463 +#: src/iac_code/ui/repl.py:467 msgid "Skip" msgstr "跳过" -#: src/iac_code/ui/repl.py:465 +#: src/iac_code/ui/repl.py:469 msgid "Continue with the current version for this session." msgstr "本次会话继续使用当前版本。" -#: src/iac_code/ui/repl.py:468 +#: src/iac_code/ui/repl.py:472 msgid "Skip until next version" msgstr "跳过直到下一个版本" -#: src/iac_code/ui/repl.py:470 +#: src/iac_code/ui/repl.py:474 msgid "Hide this update until a newer version is available." msgstr "隐藏此更新,直到有更新的版本可用。" -#: src/iac_code/ui/repl.py:489 src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 msgid "Update command failed. Continuing with the current version." msgstr "更新命令失败。将继续使用当前版本。" -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:498 msgid "Update completed. Restart iac-code to continue." msgstr "更新已完成。请重启 iac-code 以继续。" -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:536 msgid "No image in clipboard." msgstr "剪贴板中没有图像。" -#: src/iac_code/ui/repl.py:718 +#: src/iac_code/ui/repl.py:722 msgid "Usage: !" msgstr "用法:!" -#: src/iac_code/ui/repl.py:723 +#: src/iac_code/ui/repl.py:727 msgid "Shell command support is unavailable." msgstr "Shell 命令支持不可用。" -#: src/iac_code/ui/repl.py:787 +#: src/iac_code/ui/repl.py:791 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "未知技能:${name}。输入 / 可列出命令和技能。" -#: src/iac_code/ui/repl.py:789 +#: src/iac_code/ui/repl.py:793 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "未知命令:/{name}。输入 /help 查看可用命令。" -#: src/iac_code/ui/repl.py:794 +#: src/iac_code/ui/repl.py:798 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ 只能调用技能。请改用 /{name}。" -#: src/iac_code/ui/repl.py:816 src/iac_code/ui/repl.py:861 +#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 #, python-brace-format msgid "Command error: {error}" msgstr "命令错误:{error}" -#: src/iac_code/ui/repl.py:823 +#: src/iac_code/ui/repl.py:827 #, python-brace-format msgid "Command has no handler: {name}" msgstr "命令没有处理器:{name}" -#: src/iac_code/ui/repl.py:1128 +#: src/iac_code/ui/repl.py:1132 #, python-brace-format msgid "Session not found: {session_id}" msgstr "会话不存在:{session_id}" -#: src/iac_code/ui/repl.py:1147 +#: src/iac_code/ui/repl.py:1151 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -1997,31 +2077,31 @@ msgstr "" "请运行以下命令恢复:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1252 msgid "This conversation is from a different directory." msgstr "该会话来自另一个目录。" -#: src/iac_code/ui/repl.py:1188 +#: src/iac_code/ui/repl.py:1254 msgid "To resume, run:" msgstr "请运行以下命令恢复:" -#: src/iac_code/ui/repl.py:1193 +#: src/iac_code/ui/repl.py:1259 msgid "(Command copied to clipboard)" msgstr "(命令已复制到剪贴板)" -#: src/iac_code/ui/repl.py:1350 +#: src/iac_code/ui/repl.py:1416 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " "to a vision-capable model." msgstr "当前模型 {model} 不支持图像输入。请使用 /model 切换到支持视觉的模型。" -#: src/iac_code/ui/repl.py:1359 +#: src/iac_code/ui/repl.py:1425 #, python-brace-format msgid "Image error: {err}" msgstr "图像错误:{err}" -#: src/iac_code/ui/repl.py:1376 +#: src/iac_code/ui/repl.py:1442 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2279,3 +2359,6 @@ msgstr " 方式 2 - 如果无法访问 github.com,运行以下命令通过 np #~ msgid " Option 2 - npmmirror (China-friendly mirror):" #~ msgstr " 方式 2 - npmmirror(国内镜像):" +#~ msgid "Cache create" +#~ msgstr "缓存创建" + diff --git a/src/iac_code/providers/manager.py b/src/iac_code/providers/manager.py index 591baf4..0008766 100644 --- a/src/iac_code/providers/manager.py +++ b/src/iac_code/providers/manager.py @@ -233,6 +233,28 @@ def reconfigure( def get_model_name(self) -> str: return self._model + def get_provider_key(self) -> str: + """Return the runtime provider key without forcing provider creation.""" + if self._provider_key_override: + return self._provider_key_override + if self._provider is not None: + key = getattr(self._provider, "_PROVIDER_KEY", "") + if isinstance(key, str) and key: + return key + try: + return _detect_provider_name(self._model) + except ValueError: + return "" + + def get_provider_display(self) -> str: + key = self.get_provider_key() + if not key: + return "" + from iac_code.providers.registry import PROVIDER_REGISTRY + + descriptor = PROVIDER_REGISTRY.get(key) + return descriptor.display_name if descriptor is not None else key + def _get_fallback_model(self) -> str | None: return MODEL_FALLBACK_MAP.get(self._model) diff --git a/src/iac_code/services/session_index.py b/src/iac_code/services/session_index.py index d57112b..d7a3e41 100644 --- a/src/iac_code/services/session_index.py +++ b/src/iac_code/services/session_index.py @@ -14,7 +14,7 @@ from dataclasses import dataclass from pathlib import Path -from iac_code.utils.project_paths import get_project_dir, get_projects_dir, sanitize_path +from iac_code.utils.project_paths import get_project_dir, get_projects_dir, is_conversation_session_file, sanitize_path LITE_READ_BUF_SIZE = 64 * 1024 @@ -239,6 +239,8 @@ def list_for_cwd(self, cwd: str) -> list[SessionEntry]: return [] entries: list[SessionEntry] = [] for jsonl in project_dir.glob("*.jsonl"): + if not is_conversation_session_file(jsonl): + continue entry = _build_entry(jsonl, fallback_cwd=cwd) if entry is not None: entries.append(entry) @@ -254,6 +256,8 @@ def list_all_projects(self) -> list[SessionEntry]: if not proj_dir.is_dir(): continue for jsonl in proj_dir.glob("*.jsonl"): + if not is_conversation_session_file(jsonl): + continue entry = _build_entry(jsonl, fallback_cwd="") if entry is not None: entries.append(entry) @@ -269,6 +273,8 @@ def find_by_id_or_prefix(self, arg: str) -> SessionEntry | None: if not proj_dir.is_dir(): continue for jsonl in proj_dir.glob("*.jsonl"): + if not is_conversation_session_file(jsonl): + continue sid = jsonl.stem if sid == arg: return _build_entry(jsonl, fallback_cwd="") diff --git a/src/iac_code/services/session_storage.py b/src/iac_code/services/session_storage.py index 380525f..a652267 100644 --- a/src/iac_code/services/session_storage.py +++ b/src/iac_code/services/session_storage.py @@ -29,6 +29,7 @@ get_project_dir, get_projects_dir, get_session_path, + is_conversation_session_file, ) @@ -172,7 +173,7 @@ def find_session_anywhere(self, session_id: str) -> tuple[str, Path] | None: if not proj_dir.is_dir(): continue candidate = proj_dir / f"{session_id}.jsonl" - if candidate.exists(): + if candidate.exists() and is_conversation_session_file(candidate): cwd = self._read_cwd_from_file(candidate) or "" return cwd, candidate return None @@ -186,6 +187,8 @@ def get_latest_session_anywhere(self) -> tuple[str, str] | None: if not proj_dir.is_dir(): continue for jsonl in proj_dir.glob("*.jsonl"): + if not is_conversation_session_file(jsonl): + continue mtime = jsonl.stat().st_mtime if latest is None or mtime > latest[0]: latest = (mtime, jsonl) diff --git a/src/iac_code/services/session_usage.py b/src/iac_code/services/session_usage.py new file mode 100644 index 0000000..13c277f --- /dev/null +++ b/src/iac_code/services/session_usage.py @@ -0,0 +1,168 @@ +"""Session-level provider API usage persistence.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +from loguru import logger + +from iac_code.types.stream_events import Usage +from iac_code.utils.file_security import ensure_private_dir, ensure_private_file +from iac_code.utils.project_paths import get_project_dir, get_projects_dir, sanitize_path + + +@dataclass +class SessionUsageTotals: + """Cumulative provider-reported token usage for one session.""" + + input_tokens: int = 0 + output_tokens: int = 0 + cache_read_input_tokens: int = 0 + cache_creation_input_tokens: int = 0 + recorded_events: int = 0 + + @property + def total_tokens(self) -> int: + return self.input_tokens + self.output_tokens + self.cache_read_input_tokens + self.cache_creation_input_tokens + + @property + def has_recorded_usage(self) -> bool: + return self.recorded_events > 0 + + def add(self, usage: Usage) -> bool: + """Add a non-zero usage event and return whether it was recorded.""" + if _usage_is_zero(usage): + return False + self.input_tokens += int(usage.input_tokens or 0) + self.output_tokens += int(usage.output_tokens or 0) + self.cache_read_input_tokens += int(usage.cache_read_input_tokens or 0) + self.cache_creation_input_tokens += int(usage.cache_creation_input_tokens or 0) + self.recorded_events += 1 + return True + + def copy(self) -> SessionUsageTotals: + return SessionUsageTotals( + input_tokens=self.input_tokens, + output_tokens=self.output_tokens, + cache_read_input_tokens=self.cache_read_input_tokens, + cache_creation_input_tokens=self.cache_creation_input_tokens, + recorded_events=self.recorded_events, + ) + + +class SessionUsageStore: + """Persist cumulative API usage as a sidecar JSONL file.""" + + def __init__(self, projects_dir: Path | str | None = None) -> None: + self._projects_dir = Path(projects_dir) if projects_dir is not None else get_projects_dir() + + def path_for(self, cwd: str, session_id: str) -> Path: + return self._project_dir_for(cwd) / f"{session_id}.usage.jsonl" + + def append( + self, + cwd: str, + session_id: str, + usage: Usage, + *, + provider: str | None = None, + model: str | None = None, + created_at: datetime | None = None, + ) -> bool: + """Append a non-zero provider usage event.""" + if _usage_is_zero(usage): + return False + + path = self.path_for(cwd, session_id) + ensure_private_dir(path.parent) + row = _usage_to_row(usage, provider=provider, model=model, created_at=created_at) + with open(path, "a", encoding="utf-8") as f: + f.write(json.dumps(row, ensure_ascii=False) + "\n") + ensure_private_file(path) + return True + + def load(self, cwd: str, session_id: str) -> SessionUsageTotals: + """Load cumulative usage totals, skipping corrupt or unrelated rows.""" + path = self.path_for(cwd, session_id) + totals = SessionUsageTotals() + if not path.exists(): + return totals + + try: + with open(path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + try: + row = json.loads(line) + except json.JSONDecodeError: + logger.debug("Skipping corrupt usage row in {}", path) + continue + if not isinstance(row, dict) or row.get("type") != "usage": + continue + totals.add(_row_to_usage(row)) + except OSError as exc: + logger.debug("Failed to load usage sidecar {}: {}", path, exc) + return totals + + def _project_dir_for(self, cwd: str) -> Path: + if self._projects_dir == get_projects_dir(): + return get_project_dir(cwd) + return self._projects_dir / sanitize_path(cwd) + + +def _usage_is_zero(usage: Usage) -> bool: + return ( + int(usage.input_tokens or 0) == 0 + and int(usage.output_tokens or 0) == 0 + and int(usage.cache_read_input_tokens or 0) == 0 + and int(usage.cache_creation_input_tokens or 0) == 0 + ) + + +def _usage_to_row( + usage: Usage, + *, + provider: str | None, + model: str | None, + created_at: datetime | None, +) -> dict[str, Any]: + timestamp = created_at or datetime.now(timezone.utc) + if timestamp.tzinfo is None: + timestamp = timestamp.replace(tzinfo=timezone.utc) + timestamp = timestamp.astimezone(timezone.utc) + return { + "type": "usage", + "version": 1, + "created_at": timestamp.isoformat().replace("+00:00", "Z"), + "provider": provider, + "model": model, + "input_tokens": int(usage.input_tokens or 0), + "output_tokens": int(usage.output_tokens or 0), + "cache_read_input_tokens": int(usage.cache_read_input_tokens or 0), + "cache_creation_input_tokens": int(usage.cache_creation_input_tokens or 0), + } + + +def _row_to_usage(row: dict[str, Any]) -> Usage: + return Usage( + input_tokens=_int(row.get("input_tokens")), + output_tokens=_int(row.get("output_tokens")), + cache_read_input_tokens=_int(row.get("cache_read_input_tokens")), + cache_creation_input_tokens=_int(row.get("cache_creation_input_tokens")), + ) + + +def _int(value: Any) -> int: + if isinstance(value, bool): + return 0 + try: + number = int(value) + except (TypeError, ValueError): + return 0 + return max(number, 0) diff --git a/src/iac_code/ui/repl.py b/src/iac_code/ui/repl.py index 751120e..003e2ea 100644 --- a/src/iac_code/ui/repl.py +++ b/src/iac_code/ui/repl.py @@ -20,6 +20,7 @@ import time from dataclasses import dataclass from types import ModuleType +from typing import Any from loguru import logger from rich.console import Console @@ -28,10 +29,12 @@ from iac_code.agent.system_prompt import build_system_prompt from iac_code.commands import create_default_registry from iac_code.commands.registry import LocalCommand, PromptCommand -from iac_code.config import get_config_dir, get_history_path, load_credentials +from iac_code.config import get_active_provider_key, get_config_dir, get_history_path, load_credentials from iac_code.i18n import _ from iac_code.memory.memory_manager import MemoryManager from iac_code.providers.manager import ProviderManager +from iac_code.providers.registry import PROVIDER_REGISTRY +from iac_code.services.cloud_credentials import CloudCredentials from iac_code.services.session_index import SessionIndex from iac_code.services.session_storage import SessionStorage from iac_code.services.update_checker import ( @@ -128,6 +131,7 @@ def __init__( self._session_storage = SessionStorage() self.session_index = SessionIndex() self._session_id = self._resolve_session_id(resume_session_id) + self._was_resumed = resume_session_id is not None from iac_code.utils.image.store import ImageStore self._image_store = ImageStore(session_id=self._session_id) @@ -1150,6 +1154,67 @@ def _cross_project_message(cwd: str, session_id: str) -> str: def session_id(self) -> str: return self._session_id + def get_status_snapshot(self) -> dict[str, Any]: + state = self.store.get_state() + messages = self._agent_loop.context_manager.get_messages() + return { + "session_id": self._session_id, + "resumed": self._was_resumed, + "provider": self._status_provider_display(), + "model": self._status_model(state.model), + "region": self._status_region(), + "cwd": self._original_cwd, + "api_usage": self._agent_loop.get_session_usage(), + "turn_count": self._count_user_turns(messages), + "max_turns": self._agent_loop.max_turns, + "context_usage": self._agent_loop.get_context_usage(), + } + + def _status_provider_display(self) -> str: + if hasattr(self._provider_manager, "get_provider_display"): + try: + display = self._provider_manager.get_provider_display() + except Exception: + display = "" + if isinstance(display, str) and display: + return display + key = get_active_provider_key() + if not key: + return "" + descriptor = PROVIDER_REGISTRY.get(key) + if descriptor is not None: + return descriptor.display_name + return key + + def _status_model(self, fallback: str) -> str: + if hasattr(self._provider_manager, "get_model_name"): + try: + model = self._provider_manager.get_model_name() + except Exception: + model = "" + if isinstance(model, str) and model: + return model + return fallback + + @staticmethod + def _status_region() -> str: + credential = CloudCredentials().get_provider("aliyun") + return credential.region_id if credential and credential.region_id else "" + + @staticmethod + def _count_user_turns(messages: list) -> int: + from iac_code.agent.message import ToolResultBlock + + turns = 0 + for message in messages: + if getattr(message, "role", None) != "user": + continue + content = getattr(message, "content", "") + if isinstance(content, list) and any(isinstance(block, ToolResultBlock) for block in content): + continue + turns += 1 + return turns + # ------------------------------------------------------------------ # Session swap (used by /resume command) # ------------------------------------------------------------------ @@ -1160,6 +1225,7 @@ def swap_session(self, new_session_id: str) -> None: new_messages = self._session_storage.repair_interrupted(new_messages) self._agent_loop.replace_session(new_session_id, new_messages or None) self._session_id = new_session_id + self._was_resumed = True # Clear screen + scrollback, redraw banner, replay history. self.console.file.write("\033[H\033[2J\033[3J") diff --git a/src/iac_code/utils/project_paths.py b/src/iac_code/utils/project_paths.py index 70818b4..9d767b8 100644 --- a/src/iac_code/utils/project_paths.py +++ b/src/iac_code/utils/project_paths.py @@ -48,6 +48,11 @@ def get_session_path(cwd: str, session_id: str) -> Path: return get_project_dir(cwd) / f"{session_id}.jsonl" +def is_conversation_session_file(path: Path) -> bool: + """Return True for real conversation session JSONL files.""" + return path.name.endswith(".jsonl") and not path.name.endswith(".usage.jsonl") + + def _resolve_git_dir(worktree_root: str) -> str | None: """Given a worktree root, return the absolute path of its git dir. diff --git a/tests/agent/test_agent_loop_new.py b/tests/agent/test_agent_loop_new.py index b668c72..baf9852 100644 --- a/tests/agent/test_agent_loop_new.py +++ b/tests/agent/test_agent_loop_new.py @@ -125,6 +125,165 @@ async def fake_stream(messages, system, tools=None, max_tokens=8192): result = await loop.run("Hi") assert result == "Hello!" + async def test_records_non_zero_message_end_usage(self, mock_provider, mock_registry, tmp_path): + async def fake_stream(messages, system, tools=None, max_tokens=8192): + yield MessageStartEvent(message_id="m1") + yield TextDeltaEvent(text="Hello!") + yield MessageEndEvent( + stop_reason="end_turn", + usage=Usage( + input_tokens=10, + output_tokens=5, + cache_read_input_tokens=3, + cache_creation_input_tokens=2, + ), + ) + + from iac_code.services.session_usage import SessionUsageStore + + mock_provider.stream = fake_stream + store = SessionUsageStore(projects_dir=tmp_path) + loop = AgentLoop( + provider_manager=mock_provider, + system_prompt="test", + tool_registry=mock_registry, + session_id="usage-session", + cwd="/tmp/status-project", + session_usage_store=store, + ) + + events = [e async for e in loop.run_streaming("Hi")] + + assert any(isinstance(e, MessageEndEvent) for e in events) + totals = loop.get_session_usage() + assert totals.input_tokens == 10 + assert totals.output_tokens == 5 + assert totals.cache_read_input_tokens == 3 + assert totals.cache_creation_input_tokens == 2 + assert totals.recorded_events == 1 + assert store.load("/tmp/status-project", "usage-session").total_tokens == 20 + + async def test_records_usage_with_runtime_provider_key(self, mock_provider, mock_registry, tmp_path, monkeypatch): + async def fake_stream(messages, system, tools=None, max_tokens=8192): + yield MessageStartEvent(message_id="m1") + yield TextDeltaEvent(text="Hello!") + yield MessageEndEvent(stop_reason="end_turn", usage=Usage(input_tokens=10, output_tokens=5)) + + from iac_code.services.session_usage import SessionUsageStore + + monkeypatch.setattr("iac_code.config.get_active_provider_key", lambda: "openai") + mock_provider.stream = fake_stream + mock_provider.get_provider_key.return_value = "dashscope_token_plan" + mock_provider.get_model_name.return_value = "runtime-model" + store = SessionUsageStore(projects_dir=tmp_path) + loop = AgentLoop( + provider_manager=mock_provider, + system_prompt="test", + tool_registry=mock_registry, + session_id="runtime-session", + cwd="/tmp/status-project", + session_usage_store=store, + ) + + await loop.run("Hi") + + row = store.path_for("/tmp/status-project", "runtime-session").read_text(encoding="utf-8") + assert '"provider": "dashscope_token_plan"' in row + assert '"model": "runtime-model"' in row + + async def test_records_usage_from_multiple_model_calls_in_one_prompt(self, mock_provider, mock_registry, tmp_path): + call_count = 0 + + async def fake_stream(messages, system, tools=None, max_tokens=8192): + nonlocal call_count + call_count += 1 + if call_count == 1: + yield MessageStartEvent(message_id="m1") + yield ToolUseStartEvent(tool_use_id="toolu_1", name="read_file") + yield ToolUseEndEvent(tool_use_id="toolu_1", name="read_file", input={"path": "a.txt"}) + yield MessageEndEvent(stop_reason="tool_use", usage=Usage(input_tokens=10, output_tokens=5)) + return + + yield MessageStartEvent(message_id="m2") + yield TextDeltaEvent(text="After tool") + yield MessageEndEvent(stop_reason="end_turn", usage=Usage(input_tokens=7, output_tokens=3)) + + from iac_code.services.session_usage import SessionUsageStore + + mock_provider.stream = fake_stream + mock_registry.list_tools.return_value = [SimpleNamespace(name="read_file", description="Read", input_schema={})] + store = SessionUsageStore(projects_dir=tmp_path) + loop = AgentLoop( + provider_manager=mock_provider, + system_prompt="test", + tool_registry=mock_registry, + session_id="multi-call-session", + cwd="/tmp/status-project", + session_usage_store=store, + ) + loop._result_storage = MagicMock() + loop._result_storage.process.return_value = SimpleNamespace(content="processed result") + loop._tool_executor.execute_batch = AsyncMock(return_value=[ToolResult(content="raw result", is_error=False)]) + + events = [e async for e in loop.run_streaming("Hi")] + + assert call_count == 2 + assert any(isinstance(e, ToolResultEvent) for e in events) + totals = loop.get_session_usage() + assert totals.input_tokens == 17 + assert totals.output_tokens == 8 + assert totals.recorded_events == 2 + assert store.load("/tmp/status-project", "multi-call-session").total_tokens == 25 + + async def test_does_not_record_zero_message_end_usage(self, mock_provider, mock_registry, tmp_path): + async def fake_stream(messages, system, tools=None, max_tokens=8192): + yield MessageStartEvent(message_id="m1") + yield TextDeltaEvent(text="Hello!") + yield MessageEndEvent(stop_reason="end_turn", usage=Usage()) + + from iac_code.services.session_usage import SessionUsageStore + + mock_provider.stream = fake_stream + store = SessionUsageStore(projects_dir=tmp_path) + loop = AgentLoop( + provider_manager=mock_provider, + system_prompt="test", + tool_registry=mock_registry, + session_id="zero-session", + cwd="/tmp/status-project", + session_usage_store=store, + ) + + await loop.run("Hi") + + assert loop.get_session_usage().has_recorded_usage is False + assert not store.path_for("/tmp/status-project", "zero-session").exists() + + async def test_replace_session_reloads_usage_totals(self, mock_provider, mock_registry, tmp_path): + from iac_code.services.session_usage import SessionUsageStore + + store = SessionUsageStore(projects_dir=tmp_path) + store.append("/tmp/status-project", "old-session", Usage(input_tokens=1, output_tokens=2)) + store.append("/tmp/status-project", "new-session", Usage(input_tokens=7, output_tokens=8)) + + loop = AgentLoop( + provider_manager=mock_provider, + system_prompt="test", + tool_registry=mock_registry, + session_id="old-session", + cwd="/tmp/status-project", + session_usage_store=store, + ) + + assert loop.get_session_usage().total_tokens == 3 + + loop.replace_session("new-session", resume_messages=None) + + assert loop.session_id == "new-session" + assert loop.get_session_usage().input_tokens == 7 + assert loop.get_session_usage().output_tokens == 8 + assert loop.get_session_usage().total_tokens == 15 + async def test_run_streaming_executes_tools_and_applies_extensions(self, mock_provider, mock_registry): call_count = 0 @@ -506,6 +665,38 @@ async def test_auto_compact_success(self, mock_provider, mock_registry): assert event.original_tokens == 1200 assert event.compacted_tokens == 400 + async def test_auto_compact_records_response_usage(self, mock_provider, mock_registry, tmp_path): + from iac_code.services.session_usage import SessionUsageStore + + mock_provider.complete = AsyncMock( + return_value=SimpleNamespace( + text="summary", + usage=Usage(input_tokens=11, output_tokens=4, cache_read_input_tokens=2), + ) + ) + store = SessionUsageStore(projects_dir=tmp_path) + loop = AgentLoop( + provider_manager=mock_provider, + system_prompt="test", + tool_registry=mock_registry, + session_id="auto-compact-usage", + cwd="/tmp/status-project", + session_usage_store=store, + ) + loop.context_manager = MagicMock() + loop.context_manager.build_compaction_prompt.return_value = "compact me" + loop.context_manager.apply_compaction.return_value = (1200, 400) + + event = await loop._auto_compact() + + assert isinstance(event, CompactionEvent) + totals = loop.get_session_usage() + assert totals.input_tokens == 11 + assert totals.output_tokens == 4 + assert totals.cache_read_input_tokens == 2 + assert totals.recorded_events == 1 + assert store.load("/tmp/status-project", "auto-compact-usage").total_tokens == 17 + async def test_auto_compact_returns_none_without_prompt(self, mock_provider, mock_registry): loop = AgentLoop(provider_manager=mock_provider, system_prompt="test", tool_registry=mock_registry) loop.context_manager = MagicMock() @@ -526,6 +717,39 @@ async def test_compact_returns_success_with_tokens(self, mock_provider, mock_reg assert result.status == "success" assert (result.original_tokens, result.compacted_tokens) == (900, 300) + async def test_compact_records_response_usage(self, mock_provider, mock_registry, tmp_path): + from iac_code.services.session_usage import SessionUsageStore + + mock_provider.complete = AsyncMock( + return_value=SimpleNamespace( + text="summary", + usage=Usage(input_tokens=13, output_tokens=6, cache_creation_input_tokens=3), + ) + ) + store = SessionUsageStore(projects_dir=tmp_path) + loop = AgentLoop( + provider_manager=mock_provider, + system_prompt="test", + tool_registry=mock_registry, + session_id="manual-compact-usage", + cwd="/tmp/status-project", + session_usage_store=store, + ) + loop.context_manager = MagicMock() + loop.context_manager.get_messages.return_value = [object()] + loop.context_manager.build_compaction_prompt.return_value = "compact me" + loop.context_manager.apply_compaction.return_value = (900, 300) + + result = await loop.compact() + + assert result.status == "success" + totals = loop.get_session_usage() + assert totals.input_tokens == 13 + assert totals.output_tokens == 6 + assert totals.cache_creation_input_tokens == 3 + assert totals.recorded_events == 1 + assert store.load("/tmp/status-project", "manual-compact-usage").total_tokens == 22 + async def test_compact_returns_empty_when_no_messages(self, mock_provider, mock_registry): loop = AgentLoop(provider_manager=mock_provider, system_prompt="test", tool_registry=mock_registry) loop.context_manager = MagicMock() diff --git a/tests/commands/test_registry.py b/tests/commands/test_registry.py index af10b8d..d7b6a51 100644 --- a/tests/commands/test_registry.py +++ b/tests/commands/test_registry.py @@ -199,11 +199,11 @@ def test_create_default_registry_returns_registry(self): registry = create_default_registry() assert isinstance(registry, CommandRegistry) - def test_create_default_registry_has_10_commands(self): - """Test create_default_registry has 10 commands.""" + def test_create_default_registry_has_11_commands(self): + """Test create_default_registry has 11 commands.""" registry = create_default_registry() all_cmds = registry.get_all() - assert len(all_cmds) == 10 + assert len(all_cmds) == 11 def test_create_default_registry_command_names(self): """Test create_default_registry has expected command names.""" @@ -221,6 +221,7 @@ def test_create_default_registry_command_names(self): "effort", "resume", "memory", + "status", } def test_help_command_has_alias(self): diff --git a/tests/commands/test_status.py b/tests/commands/test_status.py new file mode 100644 index 0000000..ebc90df --- /dev/null +++ b/tests/commands/test_status.py @@ -0,0 +1,227 @@ +from types import SimpleNamespace +from unittest.mock import MagicMock + +import pytest +from rich.cells import cell_len +from rich.console import Console + +from iac_code.commands.status import status_command +from iac_code.i18n import setup_i18n + + +def _usage(**overrides): + values = { + "input_tokens": 0, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation_input_tokens": 0, + "total_tokens": 0, + "recorded_events": 0, + "has_recorded_usage": False, + } + values.update(overrides) + return SimpleNamespace(**values) + + +def _render_text(renderable) -> str: + console = Console(record=True, width=120, color_system=None) + console.print(renderable) + return console.export_text() + + +def _cell_index_before(rendered: str, value: str) -> int: + for line in rendered.splitlines(): + if value in line: + return cell_len(line.split(value, 1)[0]) + raise AssertionError(f"{value!r} not found in rendered output") + + +@pytest.mark.asyncio +async def test_status_requires_context() -> None: + result = await status_command() + assert "context" in result.lower() + + +@pytest.mark.asyncio +async def test_status_requires_repl() -> None: + context = MagicMock() + context.repl = None + result = await status_command(context=context) + assert "repl" in result.lower() + + +@pytest.mark.asyncio +async def test_status_prints_recorded_usage_panel() -> None: + console = MagicMock() + repl = MagicMock() + repl.get_status_snapshot.return_value = { + "session_id": "abc123", + "resumed": True, + "provider": "Alibaba Cloud Bailian", + "model": "qwen3.7-max", + "region": "cn-beijing", + "cwd": "/tmp/status-project", + "api_usage": _usage( + input_tokens=12450, + output_tokens=3280, + cache_read_input_tokens=8200, + cache_creation_input_tokens=10, + total_tokens=21940, + recorded_events=3, + has_recorded_usage=True, + ), + "turn_count": 7, + "max_turns": 100, + "context_usage": { + "total_tokens": 58000, + "context_window": 128000, + "usage_percent": 45.3125, + }, + } + context = MagicMock(console=console, repl=repl) + + result = await status_command(context=context) + + assert result is None + console.print.assert_called_once() + rendered = _render_text(console.print.call_args.args[0]) + assert "Session Status" in rendered + assert "abc123 (resumed)" in rendered + assert "Alibaba Cloud Bailian" in rendered + assert "qwen3.7-max" in rendered + assert "cn-beijing" in rendered + assert "12,450" in rendered + assert "3,280" in rendered + assert "8,200" in rendered + assert "21,940" in rendered + assert "Cache create" not in rendered + assert "7 / 100" in rendered + assert "45%" in rendered + + +@pytest.mark.asyncio +async def test_status_prints_no_recorded_usage_message() -> None: + console = MagicMock() + repl = MagicMock() + repl.get_status_snapshot.return_value = { + "session_id": "fresh", + "resumed": False, + "provider": "", + "model": "test-model", + "region": "", + "cwd": "/tmp/status-project", + "api_usage": _usage(), + "turn_count": 0, + "max_turns": 100, + "context_usage": { + "total_tokens": 0, + "context_window": 128000, + "usage_percent": 0.0, + }, + } + context = MagicMock(console=console, repl=repl) + + await status_command(context=context) + + rendered = _render_text(console.print.call_args.args[0]) + assert "not configured" in rendered + assert "No recorded API usage" in rendered + + +@pytest.mark.asyncio +async def test_status_uses_compiled_translations(monkeypatch) -> None: + monkeypatch.setenv("LANGUAGE", "zh") + setup_i18n() + try: + console = MagicMock() + repl = MagicMock() + repl.get_status_snapshot.return_value = { + "session_id": "abc123", + "resumed": True, + "provider": "dashscope", + "model": "qwen", + "region": "cn-beijing", + "cwd": "/tmp/status-project", + "api_usage": _usage(input_tokens=10, output_tokens=5, total_tokens=15, has_recorded_usage=True), + "turn_count": 1, + "max_turns": 100, + "context_usage": { + "total_tokens": 1000, + "context_window": 128000, + "usage_percent": 1.0, + }, + } + context = MagicMock(console=console, repl=repl) + + await status_command(context=context) + + rendered = _render_text(console.print.call_args.args[0]) + assert "会话状态" in rendered + assert "abc123(已恢复)" in rendered + assert "API Token 用量(已记录)" in rendered + assert "输入" in rendered + assert "缓存创建" not in rendered + finally: + monkeypatch.setenv("LANGUAGE", "en") + setup_i18n() + + +@pytest.mark.asyncio +async def test_status_aligns_translated_labels_by_display_width(monkeypatch) -> None: + monkeypatch.setenv("LANGUAGE", "zh") + setup_i18n() + try: + console = MagicMock() + repl = MagicMock() + repl.get_status_snapshot.return_value = { + "session_id": "abc123", + "resumed": True, + "provider": "dashscope", + "model": "qwen", + "region": "cn-beijing", + "cwd": "/tmp/status-project", + "api_usage": _usage( + input_tokens=43210, + output_tokens=5678, + cache_read_input_tokens=9012, + total_tokens=52800, + has_recorded_usage=True, + ), + "turn_count": 7, + "max_turns": 100, + "context_usage": { + "total_tokens": 1000, + "context_window": 128000, + "usage_percent": 1.0, + }, + } + context = MagicMock(console=console, repl=repl) + + await status_command(context=context) + + rendered = _render_text(console.print.call_args.args[0]) + main_values = [ + "abc123", + "dashscope", + "qwen", + "cn-beijing", + "/tmp/status-project", + "7 / 100", + "已使用 1%", + ] + main_starts = {_cell_index_before(rendered, value) for value in main_values} + usage_starts = { + _cell_index_before(rendered, value) + for value in [ + "43,210", + "5,678", + "9,012", + "52,800", + ] + } + + assert len(main_starts) == 1 + assert len(usage_starts) == 1 + finally: + monkeypatch.setenv("LANGUAGE", "en") + setup_i18n() diff --git a/tests/providers/test_manager.py b/tests/providers/test_manager.py index 424b190..3be398c 100644 --- a/tests/providers/test_manager.py +++ b/tests/providers/test_manager.py @@ -114,6 +114,18 @@ def test_unknown_model_no_fallback(self, monkeypatch): m = ProviderManager(model="some-model-without-fallback", credentials={}) assert m._get_fallback_model() is None + def test_provider_key_and_display_use_runtime_override(self, monkeypatch): + monkeypatch.setattr("iac_code.config.get_active_provider_key", lambda: "openai") + monkeypatch.setattr("iac_code.config.get_provider_config", lambda name: {}) + m = ProviderManager( + model="qwen3.6-plus", + credentials={"dashscope_token_plan": "tp-key"}, + provider_key_override="dashscope_token_plan", + ) + + assert m.get_provider_key() == "dashscope_token_plan" + assert m.get_provider_display() == "Alibaba Cloud Bailian Token Plan" + def test_reconfigure_swaps_model_and_credentials(self, monkeypatch): monkeypatch.setattr("iac_code.config.get_active_provider_key", lambda: "anthropic") m = ProviderManager(model="claude-sonnet-4-6", credentials={"anthropic": "old"}) diff --git a/tests/services/test_session_index.py b/tests/services/test_session_index.py index 0fd7f69..47aab38 100644 --- a/tests/services/test_session_index.py +++ b/tests/services/test_session_index.py @@ -15,6 +15,8 @@ read_lite_metadata, ) from iac_code.services.session_storage import SessionStorage +from iac_code.services.session_usage import SessionUsageStore +from iac_code.types.stream_events import Usage # --------------------------------------------------------------------------- # Field extraction helpers @@ -143,3 +145,16 @@ def test_find_by_id_or_prefix_exact_overrides_ambiguity(self, tmp_path): index = SessionIndex(projects_dir=tmp_path) entry = index.find_by_id_or_prefix("abc") assert entry is not None and entry.session_id == "abc" + + def test_ignores_usage_sidecars(self, tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + usage_store = SessionUsageStore(projects_dir=tmp_path) + storage.append("/p", "abc", Message(role="user", content="x"), git_branch=None) + usage_store.append("/p", "abc", Usage(input_tokens=10, output_tokens=5), provider="dashscope", model="qwen") + + index = SessionIndex(projects_dir=tmp_path) + + assert [entry.session_id for entry in index.list_for_cwd("/p")] == ["abc"] + assert {entry.session_id for entry in index.list_all_projects()} == {"abc"} + assert index.find_by_id_or_prefix("abc").session_id == "abc" + assert index.find_by_id_or_prefix("abc.usage") is None diff --git a/tests/services/test_session_storage.py b/tests/services/test_session_storage.py index 8dac0fc..82c025d 100644 --- a/tests/services/test_session_storage.py +++ b/tests/services/test_session_storage.py @@ -5,6 +5,8 @@ from iac_code.agent.message import Message, TextBlock, ToolResultBlock, ToolUseBlock from iac_code.services.session_storage import SessionStorage +from iac_code.services.session_usage import SessionUsageStore +from iac_code.types.stream_events import Usage CWD = "/tmp/proj-x" @@ -120,6 +122,18 @@ def test_get_latest_session_anywhere(self, storage): result = storage.get_latest_session_anywhere() assert result == ("/tmp/b", "newer") + def test_cross_project_lookup_ignores_usage_sidecars(self, storage): + import os + + usage_store = SessionUsageStore(projects_dir=storage._projects_dir) + storage.append(CWD, "real", Message(role="user", content="real"), git_branch=None) + usage_store.append(CWD, "real", Usage(input_tokens=10, output_tokens=5), provider="dashscope", model="qwen") + usage_path = usage_store.path_for(CWD, "real") + os.utime(usage_path, (usage_path.stat().st_atime, usage_path.stat().st_mtime + 100)) + + assert storage.find_session_anywhere("real.usage") is None + assert storage.get_latest_session_anywhere() == (CWD, "real") + def test_repair_interrupted_inserts_synthetic_results(self, storage): storage.append( CWD, diff --git a/tests/services/test_session_usage.py b/tests/services/test_session_usage.py new file mode 100644 index 0000000..6ff83f2 --- /dev/null +++ b/tests/services/test_session_usage.py @@ -0,0 +1,88 @@ +import json + +from iac_code.services.session_usage import SessionUsageStore, SessionUsageTotals +from iac_code.types.stream_events import Usage + +CWD = "/tmp/status-project" + + +def test_totals_adds_usage_and_tracks_record_count() -> None: + totals = SessionUsageTotals() + + totals.add(Usage(input_tokens=10, output_tokens=5, cache_read_input_tokens=3, cache_creation_input_tokens=2)) + totals.add(Usage(input_tokens=7, output_tokens=1)) + + assert totals.input_tokens == 17 + assert totals.output_tokens == 6 + assert totals.cache_read_input_tokens == 3 + assert totals.cache_creation_input_tokens == 2 + assert totals.total_tokens == 28 + assert totals.recorded_events == 2 + assert totals.has_recorded_usage is True + + +def test_all_zero_usage_is_not_recorded(tmp_path) -> None: + store = SessionUsageStore(projects_dir=tmp_path) + + recorded = store.append(CWD, "s1", Usage(), provider="dashscope", model="qwen3.7-max") + + assert recorded is False + assert store.load(CWD, "s1").has_recorded_usage is False + assert not store.path_for(CWD, "s1").exists() + + +def test_append_and_load_round_trip(tmp_path) -> None: + store = SessionUsageStore(projects_dir=tmp_path) + + assert store.append(CWD, "s2", Usage(input_tokens=12, output_tokens=3), provider="dashscope", model="qwen3.7-max") + assert store.append( + CWD, + "s2", + Usage(input_tokens=5, output_tokens=2, cache_read_input_tokens=4, cache_creation_input_tokens=1), + provider="dashscope", + model="qwen3.7-max", + ) + + totals = store.load(CWD, "s2") + assert totals.input_tokens == 17 + assert totals.output_tokens == 5 + assert totals.cache_read_input_tokens == 4 + assert totals.cache_creation_input_tokens == 1 + assert totals.total_tokens == 27 + assert totals.recorded_events == 2 + + lines = store.path_for(CWD, "s2").read_text(encoding="utf-8").splitlines() + row = json.loads(lines[0]) + assert row["type"] == "usage" + assert row["version"] == 1 + assert row["provider"] == "dashscope" + assert row["model"] == "qwen3.7-max" + assert row["created_at"].endswith("Z") + + +def test_load_skips_corrupt_and_unrelated_rows(tmp_path) -> None: + store = SessionUsageStore(projects_dir=tmp_path) + path = store.path_for(CWD, "s3") + path.parent.mkdir(parents=True) + path.write_text( + "\n".join( + [ + '{"type":"usage","version":1,"input_tokens":4,"output_tokens":6,' + '"cache_read_input_tokens":1,"cache_creation_input_tokens":0}', + "not json", + '{"type":"last-prompt","last_prompt":"ignored"}', + '{"type":"usage","version":1,"input_tokens":3,"output_tokens":2}', + ] + ) + + "\n", + encoding="utf-8", + ) + + totals = store.load(CWD, "s3") + + assert totals.input_tokens == 7 + assert totals.output_tokens == 8 + assert totals.cache_read_input_tokens == 1 + assert totals.cache_creation_input_tokens == 0 + assert totals.total_tokens == 16 + assert totals.recorded_events == 2 diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 9dac73e..3d859a8 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -12,7 +12,7 @@ import pytest from babel.messages.pofile import read_po -from iac_code.i18n import DEFAULT_LANGUAGE +from iac_code.i18n import DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES # Get the project root directory PROJECT_ROOT = Path(__file__).parent.parent @@ -159,6 +159,15 @@ def test_all_languages_have_po_files(): assert not missing_po_files, f"Missing .po files for languages: {missing_po_files}" +def test_supported_languages_match_locale_dirs(): + """Verify supported languages are the default language plus locale directories.""" + language_dirs = _discover_language_dirs() + locale_codes = {lang_dir.name for lang_dir in language_dirs} + + assert len(SUPPORTED_LANGUAGES) == 7 + assert set(SUPPORTED_LANGUAGES) == {DEFAULT_LANGUAGE, *locale_codes} + + def test_mo_files_up_to_date(): """Verify that .mo files are compiled and newer than their .po files. diff --git a/tests/ui/test_repl_status.py b/tests/ui/test_repl_status.py new file mode 100644 index 0000000..c9c48f3 --- /dev/null +++ b/tests/ui/test_repl_status.py @@ -0,0 +1,98 @@ +from types import SimpleNamespace +from unittest.mock import MagicMock + +from iac_code.agent.message import Message, ToolResultBlock +from iac_code.state.app_state import AppState, AppStateStore +from iac_code.ui.repl import InlineREPL + + +def test_count_user_turns_ignores_tool_result_messages() -> None: + messages = [ + Message(role="user", content="first"), + Message(role="assistant", content="answer"), + Message(role="user", content=[ToolResultBlock(tool_use_id="t1", content="tool", is_error=False)]), + Message(role="user", content="second"), + ] + + assert InlineREPL._count_user_turns(messages) == 2 + + +def test_status_snapshot_uses_agent_loop_and_original_cwd(monkeypatch) -> None: + repl = object.__new__(InlineREPL) + repl._session_id = "abc123" + repl._was_resumed = True + repl._original_cwd = "/tmp/status-project" + repl.store = AppStateStore(AppState(model="qwen3.7-max", cwd="/other/cwd")) + repl._provider_manager = MagicMock() + repl._provider_manager.get_provider_display.return_value = "Alibaba Cloud Bailian" + repl._provider_manager.get_model_name.return_value = "qwen3.7-max" + repl._agent_loop = MagicMock() + repl._agent_loop.max_turns = 100 + repl._agent_loop.get_session_usage.return_value = SimpleNamespace( + input_tokens=10, + output_tokens=5, + cache_read_input_tokens=2, + cache_creation_input_tokens=1, + total_tokens=18, + recorded_events=1, + has_recorded_usage=True, + ) + repl._agent_loop.get_context_usage.return_value = { + "total_tokens": 58000, + "context_window": 128000, + "usage_percent": 45.3125, + } + repl._agent_loop.context_manager.get_messages.return_value = [ + Message(role="user", content="first"), + Message(role="assistant", content="answer"), + ] + + monkeypatch.setattr("iac_code.ui.repl.get_active_provider_key", lambda: "dashscope") + monkeypatch.setattr( + "iac_code.ui.repl.CloudCredentials", + lambda: SimpleNamespace(get_provider=lambda name: SimpleNamespace(region_id="cn-beijing")), + ) + + snapshot = repl.get_status_snapshot() + + assert snapshot["session_id"] == "abc123" + assert snapshot["resumed"] is True + assert snapshot["cwd"] == "/tmp/status-project" + assert snapshot["provider"] == "Alibaba Cloud Bailian" + assert snapshot["model"] == "qwen3.7-max" + assert snapshot["region"] == "cn-beijing" + assert snapshot["turn_count"] == 1 + assert snapshot["max_turns"] == 100 + assert snapshot["api_usage"].total_tokens == 18 + assert snapshot["context_usage"]["usage_percent"] == 45.3125 + + +def test_status_snapshot_uses_runtime_provider_manager(monkeypatch) -> None: + repl = object.__new__(InlineREPL) + repl._session_id = "runtime" + repl._was_resumed = False + repl._original_cwd = "/tmp/status-project" + repl.store = AppStateStore(AppState(model="stale-model", cwd="/tmp/status-project")) + repl._provider_manager = MagicMock() + repl._provider_manager.get_provider_display.return_value = "Runtime Provider" + repl._provider_manager.get_model_name.return_value = "runtime-model" + repl._agent_loop = MagicMock() + repl._agent_loop.max_turns = 100 + repl._agent_loop.get_session_usage.return_value = SimpleNamespace( + total_tokens=0, + recorded_events=0, + has_recorded_usage=False, + ) + repl._agent_loop.get_context_usage.return_value = {} + repl._agent_loop.context_manager.get_messages.return_value = [] + + monkeypatch.setattr("iac_code.ui.repl.get_active_provider_key", lambda: "openai") + monkeypatch.setattr( + "iac_code.ui.repl.CloudCredentials", + lambda: SimpleNamespace(get_provider=lambda name: None), + ) + + snapshot = repl.get_status_snapshot() + + assert snapshot["provider"] == "Runtime Provider" + assert snapshot["model"] == "runtime-model" From e6528bac2c39091bdc89b469fe7072c99b6698d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 11:01:32 +0800 Subject: [PATCH 03/11] feat: add skills management --- src/iac_code/agent/agent_loop.py | 4 + src/iac_code/commands/__init__.py | 9 + src/iac_code/commands/registry.py | 6 + src/iac_code/commands/skills.py | 29 ++ .../i18n/locales/de/LC_MESSAGES/messages.po | 259 +++++++++++---- .../i18n/locales/es/LC_MESSAGES/messages.po | 259 +++++++++++---- .../i18n/locales/fr/LC_MESSAGES/messages.po | 257 +++++++++++---- .../i18n/locales/ja/LC_MESSAGES/messages.po | 251 +++++++++++---- .../i18n/locales/pt/LC_MESSAGES/messages.po | 257 +++++++++++---- .../i18n/locales/zh/LC_MESSAGES/messages.po | 251 +++++++++++---- src/iac_code/skills/management.py | 81 +++++ src/iac_code/skills/settings.py | 61 ++++ src/iac_code/skills/skill_tool.py | 13 + src/iac_code/ui/dialogs/skills_picker.py | 297 ++++++++++++++++++ src/iac_code/ui/repl.py | 109 ++++--- tests/agent/test_agent_loop_new.py | 16 + tests/cli/test_headless.py | 13 +- tests/commands/test_registry.py | 24 +- tests/commands/test_skills.py | 52 +++ tests/skills/test_management.py | 40 +++ tests/skills/test_settings.py | 50 +++ tests/skills/test_skill_tool.py | 21 ++ tests/ui/dialogs/test_skills_picker.py | 159 ++++++++++ tests/ui/suggestions/test_skill_provider.py | 10 + tests/ui/test_repl_integration.py | 73 +++++ 25 files changed, 2151 insertions(+), 450 deletions(-) create mode 100644 src/iac_code/commands/skills.py create mode 100644 src/iac_code/skills/management.py create mode 100644 src/iac_code/skills/settings.py create mode 100644 src/iac_code/ui/dialogs/skills_picker.py create mode 100644 tests/commands/test_skills.py create mode 100644 tests/skills/test_management.py create mode 100644 tests/skills/test_settings.py create mode 100644 tests/ui/dialogs/test_skills_picker.py diff --git a/src/iac_code/agent/agent_loop.py b/src/iac_code/agent/agent_loop.py index 3f682ec..e7ccafa 100644 --- a/src/iac_code/agent/agent_loop.py +++ b/src/iac_code/agent/agent_loop.py @@ -117,6 +117,10 @@ def set_provider(self, provider_manager: Any, system_prompt: str | None = None) self.system_prompt = system_prompt self.context_manager.set_system_prompt(system_prompt) + def set_auto_trigger_skills(self, skill_commands: list[Any] | None) -> None: + """Refresh skills considered for automatic trigger injection.""" + self._auto_trigger_skills = list(skill_commands or []) + def _get_tool_definitions(self): """Convert tool registry to provider ToolDefinition format.""" from iac_code.providers.base import ToolDefinition diff --git a/src/iac_code/commands/__init__.py b/src/iac_code/commands/__init__.py index e8c18be..ed86a12 100644 --- a/src/iac_code/commands/__init__.py +++ b/src/iac_code/commands/__init__.py @@ -11,6 +11,7 @@ from iac_code.commands.model import model_command from iac_code.commands.registry import Command, CommandRegistry, LocalCommand, PromptCommand from iac_code.commands.resume import resume_command +from iac_code.commands.skills import skills_command from iac_code.commands.status import status_command from iac_code.i18n import _ @@ -107,6 +108,14 @@ def create_default_registry() -> CommandRegistry: history_mode="session", ) ) + registry.register( + LocalCommand( + name="skills", + description=_("Manage skills"), + handler=skills_command, + history_mode="session", + ) + ) registry.register( LocalCommand( name="status", diff --git a/src/iac_code/commands/registry.py b/src/iac_code/commands/registry.py index 0541c57..001555a 100644 --- a/src/iac_code/commands/registry.py +++ b/src/iac_code/commands/registry.py @@ -127,6 +127,12 @@ def register(self, command: Command) -> None: for alias in command.aliases: self._commands[alias] = command + def clear_prompt_commands(self) -> None: + """Remove all skill-backed commands while preserving local commands.""" + for name, command in list(self._commands.items()): + if isinstance(command, PromptCommand): + del self._commands[name] + def get(self, name: str) -> Command | None: """Get command by name or alias.""" return self._commands.get(name) diff --git a/src/iac_code/commands/skills.py b/src/iac_code/commands/skills.py new file mode 100644 index 0000000..f28f99a --- /dev/null +++ b/src/iac_code/commands/skills.py @@ -0,0 +1,29 @@ +"""/skills command — manage discovered skills.""" + +from __future__ import annotations + +from typing import Any + +from iac_code.i18n import _ +from iac_code.skills.settings import save_disabled_skills + + +async def skills_command(context=None, args: list[str] | None = None, **_kwargs: Any) -> str: + """Open the interactive skills management UI.""" + if context is None or not hasattr(context, "repl"): + return _("Skills management is only available in interactive mode.") + + repl = context.repl + from iac_code.ui.dialogs.skills_picker import SkillsPicker + + picker = SkillsPicker( + list(getattr(repl, "skill_management_items", [])), + keybinding_manager=getattr(repl, "_keybinding_manager", None), + ) + disabled = picker.run() + if disabled is None: + return _("Skills update cancelled") + + save_disabled_skills(set(disabled), locked_skill_names=set(getattr(repl, "locked_skill_names", set()))) + repl.refresh_skills() + return _("Skills updated") 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 dc2474e..f593118 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:13+0800\n" +"POT-Creation-Date: 2026-06-02 20:45+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: de\n" @@ -112,8 +112,8 @@ msgstr "Verwendung: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "Der Speicher-Manager ist nicht verfügbar." -#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 -#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "Zugriff verweigert." @@ -614,59 +614,63 @@ msgstr "Verzeichnis für persistierte A2A-Routen" msgid "Save the provided routes as a route snapshot" msgstr "Speichert die angegebenen Routen als Routen-Snapshot" -#: src/iac_code/commands/__init__.py:24 +#: src/iac_code/commands/__init__.py:25 msgid "Show available commands" msgstr "Verfügbare Befehle anzeigen" -#: src/iac_code/commands/__init__.py:33 +#: src/iac_code/commands/__init__.py:34 msgid "Clear conversation history" msgstr "Konversationsverlauf löschen" -#: src/iac_code/commands/__init__.py:41 +#: src/iac_code/commands/__init__.py:42 msgid "Show or switch model" msgstr "Modell anzeigen oder wechseln" -#: src/iac_code/commands/__init__.py:50 +#: src/iac_code/commands/__init__.py:51 msgid "Show or switch thinking effort" msgstr "Thinking-Effort anzeigen oder wechseln" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compact conversation context" msgstr "Konversationskontext komprimieren" -#: src/iac_code/commands/__init__.py:61 +#: src/iac_code/commands/__init__.py:62 msgid "Compacting conversation" msgstr "Konversation wird komprimiert" -#: src/iac_code/commands/__init__.py:68 +#: src/iac_code/commands/__init__.py:69 msgid "Exit the application" msgstr "Anwendung beenden" -#: src/iac_code/commands/__init__.py:77 +#: src/iac_code/commands/__init__.py:78 msgid "Authenticate with LLM provider" msgstr "Beim LLM-Anbieter authentifizieren" -#: src/iac_code/commands/__init__.py:86 +#: src/iac_code/commands/__init__.py:87 msgid "Toggle debug logging" msgstr "Debug-Protokollierung umschalten" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:96 msgid "View and manage persistent memories" msgstr "Persistente Erinnerungen anzeigen und verwalten" -#: src/iac_code/commands/__init__.py:97 +#: src/iac_code/commands/__init__.py:98 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:104 +#: src/iac_code/commands/__init__.py:105 msgid "Resume a previous session" msgstr "Eine frühere Sitzung fortsetzen" -#: src/iac_code/commands/__init__.py:106 +#: src/iac_code/commands/__init__.py:107 msgid "[conversation id or search term]" msgstr "[Konversations-ID oder Suchbegriff]" -#: src/iac_code/commands/__init__.py:113 +#: src/iac_code/commands/__init__.py:114 +msgid "Manage skills" +msgstr "Skills verwalten" + +#: src/iac_code/commands/__init__.py:122 msgid "Show current session status" msgstr "Aktuellen Sitzungsstatus anzeigen" @@ -849,7 +853,7 @@ msgid "Credential" msgstr "Anmeldedaten" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Region" @@ -1057,81 +1061,93 @@ msgstr "Sitzung nicht gefunden: {arg}" msgid "Resume cancelled" msgstr "Fortsetzen abgebrochen" -#: src/iac_code/commands/status.py:16 +#: src/iac_code/commands/skills.py:14 +msgid "Skills management is only available in interactive mode." +msgstr "Skill-Verwaltung ist nur im interaktiven Modus verfügbar." + +#: src/iac_code/commands/skills.py:25 +msgid "Skills update cancelled" +msgstr "Skill-Aktualisierung abgebrochen" + +#: src/iac_code/commands/skills.py:29 +msgid "Skills updated" +msgstr "Skills aktualisiert" + +#: src/iac_code/commands/status.py:19 msgid "Status command requires a context." msgstr "Der Befehl status benötigt einen Kontext." -#: src/iac_code/commands/status.py:19 +#: src/iac_code/commands/status.py:22 msgid "Status command requires a REPL context." msgstr "Der Befehl status benötigt einen REPL-Kontext." -#: src/iac_code/commands/status.py:21 +#: src/iac_code/commands/status.py:24 msgid "Status is only available in interactive mode." msgstr "status ist nur im interaktiven Modus verfügbar." -#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 msgid "Session" msgstr "Sitzung" -#: src/iac_code/commands/status.py:31 +#: src/iac_code/commands/status.py:34 msgid "Provider" msgstr "Anbieter" -#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 -#: src/iac_code/commands/status.py:33 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "nicht konfiguriert" -#: src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:35 msgid "Model" msgstr "Modell" -#: src/iac_code/commands/status.py:34 +#: src/iac_code/commands/status.py:37 msgid "CWD" msgstr "Aktuelles Verzeichnis" -#: src/iac_code/commands/status.py:38 +#: src/iac_code/commands/status.py:41 msgid "API Token Usage (recorded):" msgstr "API-Token-Nutzung (aufgezeichnet):" -#: src/iac_code/commands/status.py:41 +#: src/iac_code/commands/status.py:44 msgid "Input" msgstr "Eingabe" -#: src/iac_code/commands/status.py:42 +#: src/iac_code/commands/status.py:45 msgid "Output" msgstr "Ausgabe" -#: src/iac_code/commands/status.py:43 +#: src/iac_code/commands/status.py:46 msgid "Cache read" msgstr "Cache-Lesezugriffe" -#: src/iac_code/commands/status.py:44 +#: src/iac_code/commands/status.py:47 msgid "Total" msgstr "Gesamt" -#: src/iac_code/commands/status.py:47 +#: src/iac_code/commands/status.py:50 msgid "No recorded API usage for this session yet." msgstr "Für diese Sitzung wurde noch keine API-Nutzung aufgezeichnet." -#: src/iac_code/commands/status.py:51 +#: src/iac_code/commands/status.py:54 msgid "Turns" msgstr "Runden" -#: src/iac_code/commands/status.py:52 +#: src/iac_code/commands/status.py:55 msgid "Context" msgstr "Kontext" -#: src/iac_code/commands/status.py:54 +#: src/iac_code/commands/status.py:57 msgid "Session Status" msgstr "Sitzungsstatus" -#: src/iac_code/commands/status.py:68 +#: src/iac_code/commands/status.py:73 #, python-brace-format msgid "{session_id} (resumed)" msgstr "{session_id} (fortgesetzt)" -#: src/iac_code/commands/status.py:76 +#: src/iac_code/commands/status.py:81 #, python-brace-format msgid "{percent} used ({total} / {window})" msgstr "{percent} verwendet ({total} / {window})" @@ -1325,15 +1341,27 @@ msgstr "Ungültiges --permission-mode {!r}. Gültige Werte: {}" msgid "Allow {}?" msgstr "{} erlauben?" -#: src/iac_code/skills/skill_tool.py:130 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#, python-brace-format +msgid "Skill '{name}' is disabled. Run /skills to enable it." +msgstr "" +"Skill '{name}' ist deaktiviert. Führen Sie /skills aus, um ihn zu " +"aktivieren." + +#: src/iac_code/skills/skill_tool.py:137 #, python-brace-format msgid "Skill '{name}' loaded (inline)." msgstr "Skill '{name}' geladen (inline)." -#: src/iac_code/skills/skill_tool.py:214 +#: src/iac_code/skills/skill_tool.py:221 msgid "Skill" msgstr "Skill" +#: src/iac_code/skills/skill_tool.py:245 +#, python-brace-format +msgid "Skill disabled: {name}" +msgstr "Skill deaktiviert: {name}" + #: src/iac_code/skills/bundled/simplify.py:25 msgid "" "Review changed code for reuse, quality, and efficiency, then fix issues " @@ -2041,103 +2069,103 @@ msgstr "Nein, immer \"{rule}\" ablehnen (diese Sitzung)" msgid "No, always reject this tool" msgstr "Nein, dieses Tool immer ablehnen" -#: src/iac_code/ui/repl.py:374 +#: src/iac_code/ui/repl.py:406 msgid "Press Ctrl+C again to exit." msgstr "Drücken Sie erneut Ctrl+C zum Beenden." -#: src/iac_code/ui/repl.py:399 +#: src/iac_code/ui/repl.py:431 msgid "Interrupted." msgstr "Unterbrochen." -#: src/iac_code/ui/repl.py:436 +#: src/iac_code/ui/repl.py:468 msgid "Goodbye!" msgstr "Auf Wiedersehen!" -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:469 msgid "Resume this session with:" msgstr "Diese Sitzung fortsetzen mit:" -#: src/iac_code/ui/repl.py:462 +#: src/iac_code/ui/repl.py:494 msgid "Update now" msgstr "Jetzt aktualisieren" -#: src/iac_code/ui/repl.py:464 +#: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Führt den angezeigten Aktualisierungsbefehl aus und beendet das Programm " "bei Erfolg." -#: src/iac_code/ui/repl.py:467 +#: src/iac_code/ui/repl.py:499 msgid "Skip" msgstr "Überspringen" -#: src/iac_code/ui/repl.py:469 +#: src/iac_code/ui/repl.py:501 msgid "Continue with the current version for this session." msgstr "Für diese Sitzung mit der aktuellen Version fortfahren." -#: src/iac_code/ui/repl.py:472 +#: src/iac_code/ui/repl.py:504 msgid "Skip until next version" msgstr "Bis zur nächsten Version überspringen" -#: src/iac_code/ui/repl.py:474 +#: src/iac_code/ui/repl.py:506 msgid "Hide this update until a newer version is available." msgstr "Dieses Update ausblenden, bis eine neuere Version verfügbar ist." -#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 +#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." msgstr "" "Der Aktualisierungsbefehl ist fehlgeschlagen. Es wird mit der aktuellen " "Version fortgefahren." -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:530 msgid "Update completed. Restart iac-code to continue." msgstr "Update abgeschlossen. Starten Sie iac-code neu, um fortzufahren." -#: src/iac_code/ui/repl.py:536 +#: src/iac_code/ui/repl.py:568 msgid "No image in clipboard." msgstr "Kein Bild in der Zwischenablage." -#: src/iac_code/ui/repl.py:722 +#: src/iac_code/ui/repl.py:754 msgid "Usage: !" msgstr "Verwendung: !" -#: src/iac_code/ui/repl.py:727 +#: src/iac_code/ui/repl.py:759 msgid "Shell command support is unavailable." msgstr "Shell-Befehlsunterstützung ist nicht verfügbar." -#: src/iac_code/ui/repl.py:791 +#: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "Unbekannter Skill: ${name}. Tippe /, um Befehle und Skills aufzulisten." -#: src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available commands.Unbekannter " "Befehl: /{name}. Geben Sie /help für verfügbare Befehle ein." -#: src/iac_code/ui/repl.py:798 +#: src/iac_code/ui/repl.py:833 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ ruft nur Skills auf. Verwende stattdessen /{name}." -#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 +#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 #, python-brace-format msgid "Command error: {error}" msgstr "Befehlsfehler: {error}" -#: src/iac_code/ui/repl.py:827 +#: src/iac_code/ui/repl.py:862 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Kein Handler für Befehl: {name}" -#: src/iac_code/ui/repl.py:1132 +#: src/iac_code/ui/repl.py:1167 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sitzung nicht gefunden: {session_id}" -#: src/iac_code/ui/repl.py:1151 +#: src/iac_code/ui/repl.py:1186 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2148,19 +2176,19 @@ msgstr "" "Zum Fortsetzen ausführen:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1252 +#: src/iac_code/ui/repl.py:1287 msgid "This conversation is from a different directory." msgstr "Diese Konversation stammt aus einem anderen Verzeichnis." -#: src/iac_code/ui/repl.py:1254 +#: src/iac_code/ui/repl.py:1289 msgid "To resume, run:" msgstr "Zum Fortsetzen ausführen:" -#: src/iac_code/ui/repl.py:1259 +#: src/iac_code/ui/repl.py:1294 msgid "(Command copied to clipboard)" msgstr "(Befehl in die Zwischenablage kopiert)" -#: src/iac_code/ui/repl.py:1416 +#: src/iac_code/ui/repl.py:1451 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2169,12 +2197,12 @@ msgstr "" "Das aktuelle Modell {model} unterstützt keine Bildeingabe. Verwenden Sie " "/model, um zu einem Vision-fähigen Modell zu wechseln." -#: src/iac_code/ui/repl.py:1425 +#: src/iac_code/ui/repl.py:1460 #, python-brace-format msgid "Image error: {err}" msgstr "Bildfehler: {err}" -#: src/iac_code/ui/repl.py:1442 +#: src/iac_code/ui/repl.py:1477 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2355,6 +2383,91 @@ msgstr "vor {n} Stunde{s}" msgid "{n} day{s} ago" msgstr "vor {n} Tag{s}" +#: src/iac_code/ui/dialogs/skills_picker.py:52 +msgid "Search skills..." +msgstr "Skills suchen..." + +#: src/iac_code/ui/dialogs/skills_picker.py:159 +msgid "Skills" +msgstr "Skills" + +#: src/iac_code/ui/dialogs/skills_picker.py:161 +#, python-brace-format +msgid "{current} of {total}" +msgstr "{current} von {total}" + +#: src/iac_code/ui/dialogs/skills_picker.py:165 +#, python-brace-format +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "" +"{count} Skills - Leertaste zum Umschalten, Enter zum Speichern, Tab zum " +"Sortieren, Esc zum Abbrechen" + +#: src/iac_code/ui/dialogs/skills_picker.py:171 +#, python-brace-format +msgid "Sort: {mode}" +msgstr "Sortieren: {mode}" + +#: src/iac_code/ui/dialogs/skills_picker.py:176 +msgid "No skills found" +msgstr "Keine Skills gefunden" + +#: src/iac_code/ui/dialogs/skills_picker.py:245 +msgid "Bundled skills cannot be disabled." +msgstr "Gebündelte Skills können nicht deaktiviert werden." + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "on" +msgstr "aktiviert" + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "off" +msgstr "deaktiviert" + +#: src/iac_code/ui/dialogs/skills_picker.py:265 +msgid "locked" +msgstr "gesperrt" + +#: src/iac_code/ui/dialogs/skills_picker.py:268 +msgid "matched description" +msgstr "Beschreibung stimmt überein" + +#: src/iac_code/ui/dialogs/skills_picker.py:277 +msgid "source" +msgstr "Quelle" + +#: src/iac_code/ui/dialogs/skills_picker.py:279 +msgid "size" +msgstr "Größe" + +#: src/iac_code/ui/dialogs/skills_picker.py:280 +msgid "name" +msgstr "Name" + +#: src/iac_code/ui/dialogs/skills_picker.py:285 +msgid "bundled" +msgstr "gebündelt" + +#: src/iac_code/ui/dialogs/skills_picker.py:287 +msgid "project" +msgstr "Projekt" + +#: src/iac_code/ui/dialogs/skills_picker.py:289 +msgid "user" +msgstr "Benutzer" + +#: src/iac_code/ui/dialogs/skills_picker.py:296 +#, python-brace-format +msgid "~{count}k tokens" +msgstr "~{count}k Tokens" + +#: src/iac_code/ui/dialogs/skills_picker.py:297 +#, python-brace-format +msgid "~{count} tokens" +msgstr "~{count} Tokens" + #: src/iac_code/ui/suggestions/command_provider.py:79 msgid "Search saved memories" msgstr "Gespeicherte Erinnerungen durchsuchen" @@ -2444,3 +2557,13 @@ msgstr "" #~ msgid "Cache create" #~ msgstr "Cache-Erstellung" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "" +#~ "{count} Skills - Leertaste zum " +#~ "Umschalten, Enter zum Speichern, / zum" +#~ " Suchen, t zum Sortieren, Esc zum " +#~ "Abbrechen" + 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 8f0c861..08f4e29 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:13+0800\n" +"POT-Creation-Date: 2026-06-02 20:45+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: es\n" @@ -115,8 +115,8 @@ msgstr "Uso: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "El gestor de memoria no está disponible." -#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 -#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "Permiso denegado." @@ -613,59 +613,63 @@ msgstr "Directorio para rutas A2A persistentes" msgid "Save the provided routes as a route snapshot" msgstr "Guarda las rutas proporcionadas como una instantánea de rutas" -#: src/iac_code/commands/__init__.py:24 +#: src/iac_code/commands/__init__.py:25 msgid "Show available commands" msgstr "Mostrar los comandos disponibles" -#: src/iac_code/commands/__init__.py:33 +#: src/iac_code/commands/__init__.py:34 msgid "Clear conversation history" msgstr "Borrar el historial de conversación" -#: src/iac_code/commands/__init__.py:41 +#: src/iac_code/commands/__init__.py:42 msgid "Show or switch model" msgstr "Mostrar o cambiar de modelo" -#: src/iac_code/commands/__init__.py:50 +#: src/iac_code/commands/__init__.py:51 msgid "Show or switch thinking effort" msgstr "Mostrar o cambiar el nivel de razonamiento (effort)" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compact conversation context" msgstr "Compactar el contexto de la conversación" -#: src/iac_code/commands/__init__.py:61 +#: src/iac_code/commands/__init__.py:62 msgid "Compacting conversation" msgstr "Compactando la conversación" -#: src/iac_code/commands/__init__.py:68 +#: src/iac_code/commands/__init__.py:69 msgid "Exit the application" msgstr "Salir de la aplicación" -#: src/iac_code/commands/__init__.py:77 +#: src/iac_code/commands/__init__.py:78 msgid "Authenticate with LLM provider" msgstr "Autenticar con el proveedor LLM" -#: src/iac_code/commands/__init__.py:86 +#: src/iac_code/commands/__init__.py:87 msgid "Toggle debug logging" msgstr "Activar o desactivar el registro de depuración" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:96 msgid "View and manage persistent memories" msgstr "Ver y administrar memorias persistentes" -#: src/iac_code/commands/__init__.py:97 +#: src/iac_code/commands/__init__.py:98 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:104 +#: src/iac_code/commands/__init__.py:105 msgid "Resume a previous session" msgstr "Reanudar una sesión anterior" -#: src/iac_code/commands/__init__.py:106 +#: src/iac_code/commands/__init__.py:107 msgid "[conversation id or search term]" msgstr "[id de conversación o término de búsqueda]" -#: src/iac_code/commands/__init__.py:113 +#: src/iac_code/commands/__init__.py:114 +msgid "Manage skills" +msgstr "Gestionar habilidades" + +#: src/iac_code/commands/__init__.py:122 msgid "Show current session status" msgstr "Mostrar el estado actual de la sesión" @@ -848,7 +852,7 @@ msgid "Credential" msgstr "Credencial" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Región" @@ -1058,81 +1062,93 @@ msgstr "Sesión no encontrada: {arg}" msgid "Resume cancelled" msgstr "Reanudación cancelada" -#: src/iac_code/commands/status.py:16 +#: src/iac_code/commands/skills.py:14 +msgid "Skills management is only available in interactive mode." +msgstr "La gestión de habilidades solo está disponible en modo interactivo." + +#: src/iac_code/commands/skills.py:25 +msgid "Skills update cancelled" +msgstr "Actualización de habilidades cancelada" + +#: src/iac_code/commands/skills.py:29 +msgid "Skills updated" +msgstr "Habilidades actualizadas" + +#: src/iac_code/commands/status.py:19 msgid "Status command requires a context." msgstr "El comando status requiere un contexto." -#: src/iac_code/commands/status.py:19 +#: src/iac_code/commands/status.py:22 msgid "Status command requires a REPL context." msgstr "El comando status requiere un contexto REPL." -#: src/iac_code/commands/status.py:21 +#: src/iac_code/commands/status.py:24 msgid "Status is only available in interactive mode." msgstr "status solo está disponible en modo interactivo." -#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 msgid "Session" msgstr "Sesión" -#: src/iac_code/commands/status.py:31 +#: src/iac_code/commands/status.py:34 msgid "Provider" msgstr "Proveedor" -#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 -#: src/iac_code/commands/status.py:33 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "no configurado" -#: src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:35 msgid "Model" msgstr "Modelo" -#: src/iac_code/commands/status.py:34 +#: src/iac_code/commands/status.py:37 msgid "CWD" msgstr "Directorio actual" -#: src/iac_code/commands/status.py:38 +#: src/iac_code/commands/status.py:41 msgid "API Token Usage (recorded):" msgstr "Uso de tokens de API (registrado):" -#: src/iac_code/commands/status.py:41 +#: src/iac_code/commands/status.py:44 msgid "Input" msgstr "Entrada" -#: src/iac_code/commands/status.py:42 +#: src/iac_code/commands/status.py:45 msgid "Output" msgstr "Salida" -#: src/iac_code/commands/status.py:43 +#: src/iac_code/commands/status.py:46 msgid "Cache read" msgstr "Lectura de caché" -#: src/iac_code/commands/status.py:44 +#: src/iac_code/commands/status.py:47 msgid "Total" msgstr "Total" -#: src/iac_code/commands/status.py:47 +#: src/iac_code/commands/status.py:50 msgid "No recorded API usage for this session yet." msgstr "Aún no hay uso de API registrado para esta sesión." -#: src/iac_code/commands/status.py:51 +#: src/iac_code/commands/status.py:54 msgid "Turns" msgstr "Turnos" -#: src/iac_code/commands/status.py:52 +#: src/iac_code/commands/status.py:55 msgid "Context" msgstr "Contexto" -#: src/iac_code/commands/status.py:54 +#: src/iac_code/commands/status.py:57 msgid "Session Status" msgstr "Estado de la sesión" -#: src/iac_code/commands/status.py:68 +#: src/iac_code/commands/status.py:73 #, python-brace-format msgid "{session_id} (resumed)" msgstr "{session_id} (reanudada)" -#: src/iac_code/commands/status.py:76 +#: src/iac_code/commands/status.py:81 #, python-brace-format msgid "{percent} used ({total} / {window})" msgstr "{percent} usado ({total} / {window})" @@ -1323,15 +1339,27 @@ msgstr "Modo --permission-mode no válido: {!r}. Valores válidos: {}" msgid "Allow {}?" msgstr "¿Permitir {}?" -#: src/iac_code/skills/skill_tool.py:130 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#, python-brace-format +msgid "Skill '{name}' is disabled. Run /skills to enable it." +msgstr "" +"La habilidad '{name}' está deshabilitada. Ejecute /skills para " +"habilitarla." + +#: src/iac_code/skills/skill_tool.py:137 #, python-brace-format msgid "Skill '{name}' loaded (inline)." msgstr "Skill '{name}' cargado (en línea)." -#: src/iac_code/skills/skill_tool.py:214 +#: src/iac_code/skills/skill_tool.py:221 msgid "Skill" msgstr "Skill" +#: src/iac_code/skills/skill_tool.py:245 +#, python-brace-format +msgid "Skill disabled: {name}" +msgstr "Habilidad deshabilitada: {name}" + #: src/iac_code/skills/bundled/simplify.py:25 msgid "" "Review changed code for reuse, quality, and efficiency, then fix issues " @@ -2046,78 +2074,78 @@ msgstr "No, siempre denegar \"{rule}\" (esta sesión)" msgid "No, always reject this tool" msgstr "No, rechazar siempre esta herramienta" -#: src/iac_code/ui/repl.py:374 +#: src/iac_code/ui/repl.py:406 msgid "Press Ctrl+C again to exit." msgstr "Pulse Ctrl+C de nuevo para salir." -#: src/iac_code/ui/repl.py:399 +#: src/iac_code/ui/repl.py:431 msgid "Interrupted." msgstr "Interrumpido." -#: src/iac_code/ui/repl.py:436 +#: src/iac_code/ui/repl.py:468 msgid "Goodbye!" msgstr "¡Hasta luego!" -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:469 msgid "Resume this session with:" msgstr "Para reanudar esta sesión, ejecute:" -#: src/iac_code/ui/repl.py:462 +#: src/iac_code/ui/repl.py:494 msgid "Update now" msgstr "Actualizar ahora" -#: src/iac_code/ui/repl.py:464 +#: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Ejecuta el comando de actualización mostrado y sale cuando finalice " "correctamente." -#: src/iac_code/ui/repl.py:467 +#: src/iac_code/ui/repl.py:499 msgid "Skip" msgstr "Omitir" -#: src/iac_code/ui/repl.py:469 +#: src/iac_code/ui/repl.py:501 msgid "Continue with the current version for this session." msgstr "Continuar con la versión actual durante esta sesión." -#: src/iac_code/ui/repl.py:472 +#: src/iac_code/ui/repl.py:504 msgid "Skip until next version" msgstr "Omitir hasta la siguiente versión" -#: src/iac_code/ui/repl.py:474 +#: src/iac_code/ui/repl.py:506 msgid "Hide this update until a newer version is available." msgstr "" "Ocultar esta actualización hasta que haya una versión más nueva " "disponible." -#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 +#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." msgstr "El comando de actualización falló. Se continuará con la versión actual." -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:530 msgid "Update completed. Restart iac-code to continue." msgstr "Actualización completada. Reinicia iac-code para continuar." -#: src/iac_code/ui/repl.py:536 +#: src/iac_code/ui/repl.py:568 msgid "No image in clipboard." msgstr "No hay ninguna imagen en el portapapeles." -#: src/iac_code/ui/repl.py:722 +#: src/iac_code/ui/repl.py:754 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:727 +#: src/iac_code/ui/repl.py:759 msgid "Shell command support is unavailable." msgstr "La compatibilidad con comandos de shell no está disponible." -#: src/iac_code/ui/repl.py:791 +#: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Habilidad desconocida: ${name}. Escribe / para listar comandos y " "habilidades." -#: src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" @@ -2125,27 +2153,27 @@ msgstr "" "command: /{name}. Type /help for available commands.Comando desconocido: " "/{name}. Escriba /help para ver los comandos disponibles." -#: src/iac_code/ui/repl.py:798 +#: src/iac_code/ui/repl.py:833 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ solo invoca habilidades. Usa /{name} en su lugar." -#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 +#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 #, python-brace-format msgid "Command error: {error}" msgstr "Error de comando: {error}" -#: src/iac_code/ui/repl.py:827 +#: src/iac_code/ui/repl.py:862 #, python-brace-format msgid "Command has no handler: {name}" msgstr "El comando no tiene controlador: {name}" -#: src/iac_code/ui/repl.py:1132 +#: src/iac_code/ui/repl.py:1167 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sesión no encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1151 +#: src/iac_code/ui/repl.py:1186 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2156,19 +2184,19 @@ msgstr "" "Para reanudar, ejecute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1252 +#: src/iac_code/ui/repl.py:1287 msgid "This conversation is from a different directory." msgstr "Esta conversación procede de otro directorio." -#: src/iac_code/ui/repl.py:1254 +#: src/iac_code/ui/repl.py:1289 msgid "To resume, run:" msgstr "Para reanudar, ejecute:" -#: src/iac_code/ui/repl.py:1259 +#: src/iac_code/ui/repl.py:1294 msgid "(Command copied to clipboard)" msgstr "(Comando copiado al portapapeles)" -#: src/iac_code/ui/repl.py:1416 +#: src/iac_code/ui/repl.py:1451 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2177,12 +2205,12 @@ msgstr "" "El modelo actual {model} no admite entrada de imágenes. Usa /model para " "cambiar a un modelo con capacidad de visión." -#: src/iac_code/ui/repl.py:1425 +#: src/iac_code/ui/repl.py:1460 #, python-brace-format msgid "Image error: {err}" msgstr "Error de imagen: {err}" -#: src/iac_code/ui/repl.py:1442 +#: src/iac_code/ui/repl.py:1477 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2363,6 +2391,91 @@ msgstr "hace {n} hora{s}" msgid "{n} day{s} ago" msgstr "hace {n} día{s}" +#: src/iac_code/ui/dialogs/skills_picker.py:52 +msgid "Search skills..." +msgstr "Buscar habilidades..." + +#: src/iac_code/ui/dialogs/skills_picker.py:159 +msgid "Skills" +msgstr "Habilidades" + +#: src/iac_code/ui/dialogs/skills_picker.py:161 +#, python-brace-format +msgid "{current} of {total}" +msgstr "{current} de {total}" + +#: src/iac_code/ui/dialogs/skills_picker.py:165 +#, python-brace-format +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "" +"{count} habilidades - Espacio para alternar, Enter para guardar, Tab para" +" ordenar, Esc para cancelar" + +#: src/iac_code/ui/dialogs/skills_picker.py:171 +#, python-brace-format +msgid "Sort: {mode}" +msgstr "Ordenar: {mode}" + +#: src/iac_code/ui/dialogs/skills_picker.py:176 +msgid "No skills found" +msgstr "No se encontraron habilidades" + +#: src/iac_code/ui/dialogs/skills_picker.py:245 +msgid "Bundled skills cannot be disabled." +msgstr "Las habilidades integradas no se pueden deshabilitar." + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "on" +msgstr "activada" + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "off" +msgstr "desactivada" + +#: src/iac_code/ui/dialogs/skills_picker.py:265 +msgid "locked" +msgstr "bloqueada" + +#: src/iac_code/ui/dialogs/skills_picker.py:268 +msgid "matched description" +msgstr "coincide con la descripción" + +#: src/iac_code/ui/dialogs/skills_picker.py:277 +msgid "source" +msgstr "origen" + +#: src/iac_code/ui/dialogs/skills_picker.py:279 +msgid "size" +msgstr "tamaño" + +#: src/iac_code/ui/dialogs/skills_picker.py:280 +msgid "name" +msgstr "nombre" + +#: src/iac_code/ui/dialogs/skills_picker.py:285 +msgid "bundled" +msgstr "integrada" + +#: src/iac_code/ui/dialogs/skills_picker.py:287 +msgid "project" +msgstr "proyecto" + +#: src/iac_code/ui/dialogs/skills_picker.py:289 +msgid "user" +msgstr "usuario" + +#: src/iac_code/ui/dialogs/skills_picker.py:296 +#, python-brace-format +msgid "~{count}k tokens" +msgstr "~{count}k tokens" + +#: src/iac_code/ui/dialogs/skills_picker.py:297 +#, python-brace-format +msgid "~{count} tokens" +msgstr "~{count} tokens" + #: src/iac_code/ui/suggestions/command_provider.py:79 msgid "Search saved memories" msgstr "Buscar memorias guardadas" @@ -2452,3 +2565,13 @@ msgstr "" #~ msgid "Cache create" #~ msgstr "Creación de caché" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "" +#~ "{count} habilidades - Espacio para " +#~ "alternar, Enter para guardar, / para " +#~ "buscar, t para ordenar, Esc para " +#~ "cancelar" + 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 de47e0c..bf77cbd 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:13+0800\n" +"POT-Creation-Date: 2026-06-02 20:45+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: fr\n" @@ -112,8 +112,8 @@ msgstr "Utilisation : /debug [on|off]" msgid "Memory manager is unavailable." msgstr "Le gestionnaire de mémoire est indisponible." -#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 -#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "Permission refusée." @@ -611,59 +611,63 @@ msgstr "Répertoire pour les routes A2A persistées" msgid "Save the provided routes as a route snapshot" msgstr "Enregistre les routes fournies sous forme d'instantané de routes" -#: src/iac_code/commands/__init__.py:24 +#: src/iac_code/commands/__init__.py:25 msgid "Show available commands" msgstr "Afficher les commandes disponibles" -#: src/iac_code/commands/__init__.py:33 +#: src/iac_code/commands/__init__.py:34 msgid "Clear conversation history" msgstr "Effacer l’historique de conversation" -#: src/iac_code/commands/__init__.py:41 +#: src/iac_code/commands/__init__.py:42 msgid "Show or switch model" msgstr "Afficher ou changer de modèle" -#: src/iac_code/commands/__init__.py:50 +#: src/iac_code/commands/__init__.py:51 msgid "Show or switch thinking effort" msgstr "Afficher ou modifier l’effort de raisonnement" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compact conversation context" msgstr "Compacter le contexte de conversation" -#: src/iac_code/commands/__init__.py:61 +#: src/iac_code/commands/__init__.py:62 msgid "Compacting conversation" msgstr "Compactage de la conversation" -#: src/iac_code/commands/__init__.py:68 +#: src/iac_code/commands/__init__.py:69 msgid "Exit the application" msgstr "Quitter l’application" -#: src/iac_code/commands/__init__.py:77 +#: src/iac_code/commands/__init__.py:78 msgid "Authenticate with LLM provider" msgstr "S’authentifier auprès du fournisseur LLM" -#: src/iac_code/commands/__init__.py:86 +#: src/iac_code/commands/__init__.py:87 msgid "Toggle debug logging" msgstr "Activer ou désactiver la journalisation debug" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:96 msgid "View and manage persistent memories" msgstr "Afficher et gérer les mémoires persistantes" -#: src/iac_code/commands/__init__.py:97 +#: src/iac_code/commands/__init__.py:98 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:104 +#: src/iac_code/commands/__init__.py:105 msgid "Resume a previous session" msgstr "Reprendre une session précédente" -#: src/iac_code/commands/__init__.py:106 +#: src/iac_code/commands/__init__.py:107 msgid "[conversation id or search term]" msgstr "[identifiant de conversation ou terme de recherche]" -#: src/iac_code/commands/__init__.py:113 +#: src/iac_code/commands/__init__.py:114 +msgid "Manage skills" +msgstr "Gérer les compétences" + +#: src/iac_code/commands/__init__.py:122 msgid "Show current session status" msgstr "Afficher l’état actuel de la session" @@ -846,7 +850,7 @@ msgid "Credential" msgstr "Identifiants" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Région" @@ -1060,81 +1064,93 @@ msgstr "Session introuvable : {arg}" msgid "Resume cancelled" msgstr "Reprise annulée" -#: src/iac_code/commands/status.py:16 +#: src/iac_code/commands/skills.py:14 +msgid "Skills management is only available in interactive mode." +msgstr "La gestion des compétences n'est disponible qu'en mode interactif." + +#: src/iac_code/commands/skills.py:25 +msgid "Skills update cancelled" +msgstr "Mise à jour des compétences annulée" + +#: src/iac_code/commands/skills.py:29 +msgid "Skills updated" +msgstr "Compétences mises à jour" + +#: src/iac_code/commands/status.py:19 msgid "Status command requires a context." msgstr "La commande status nécessite un contexte." -#: src/iac_code/commands/status.py:19 +#: src/iac_code/commands/status.py:22 msgid "Status command requires a REPL context." msgstr "La commande status nécessite un contexte REPL." -#: src/iac_code/commands/status.py:21 +#: src/iac_code/commands/status.py:24 msgid "Status is only available in interactive mode." msgstr "status n’est disponible qu’en mode interactif." -#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 msgid "Session" msgstr "Session" -#: src/iac_code/commands/status.py:31 +#: src/iac_code/commands/status.py:34 msgid "Provider" msgstr "Fournisseur" -#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 -#: src/iac_code/commands/status.py:33 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "non configuré" -#: src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:35 msgid "Model" msgstr "Modèle" -#: src/iac_code/commands/status.py:34 +#: src/iac_code/commands/status.py:37 msgid "CWD" msgstr "Répertoire courant" -#: src/iac_code/commands/status.py:38 +#: src/iac_code/commands/status.py:41 msgid "API Token Usage (recorded):" msgstr "Utilisation des tokens API (enregistrée) :" -#: src/iac_code/commands/status.py:41 +#: src/iac_code/commands/status.py:44 msgid "Input" msgstr "Entrée" -#: src/iac_code/commands/status.py:42 +#: src/iac_code/commands/status.py:45 msgid "Output" msgstr "Sortie" -#: src/iac_code/commands/status.py:43 +#: src/iac_code/commands/status.py:46 msgid "Cache read" msgstr "Lecture du cache" -#: src/iac_code/commands/status.py:44 +#: src/iac_code/commands/status.py:47 msgid "Total" msgstr "Total" -#: src/iac_code/commands/status.py:47 +#: src/iac_code/commands/status.py:50 msgid "No recorded API usage for this session yet." msgstr "Aucune utilisation d’API enregistrée pour cette session pour le moment." -#: src/iac_code/commands/status.py:51 +#: src/iac_code/commands/status.py:54 msgid "Turns" msgstr "Tours" -#: src/iac_code/commands/status.py:52 +#: src/iac_code/commands/status.py:55 msgid "Context" msgstr "Contexte" -#: src/iac_code/commands/status.py:54 +#: src/iac_code/commands/status.py:57 msgid "Session Status" msgstr "État de la session" -#: src/iac_code/commands/status.py:68 +#: src/iac_code/commands/status.py:73 #, python-brace-format msgid "{session_id} (resumed)" msgstr "{session_id} (reprise)" -#: src/iac_code/commands/status.py:76 +#: src/iac_code/commands/status.py:81 #, python-brace-format msgid "{percent} used ({total} / {window})" msgstr "{percent} utilisé ({total} / {window})" @@ -1326,15 +1342,25 @@ msgstr "--permission-mode invalide : {!r}. Valeurs valides : {}" msgid "Allow {}?" msgstr "Autoriser {} ?" -#: src/iac_code/skills/skill_tool.py:130 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#, python-brace-format +msgid "Skill '{name}' is disabled. Run /skills to enable it." +msgstr "La compétence « {name} » est désactivée. Exécutez /skills pour l'activer." + +#: src/iac_code/skills/skill_tool.py:137 #, python-brace-format msgid "Skill '{name}' loaded (inline)." msgstr "Skill « {name} » chargé (inline)." -#: src/iac_code/skills/skill_tool.py:214 +#: src/iac_code/skills/skill_tool.py:221 msgid "Skill" msgstr "Skill" +#: src/iac_code/skills/skill_tool.py:245 +#, python-brace-format +msgid "Skill disabled: {name}" +msgstr "Compétence désactivée : {name}" + #: src/iac_code/skills/bundled/simplify.py:25 msgid "" "Review changed code for reuse, quality, and efficiency, then fix issues " @@ -2050,103 +2076,103 @@ msgstr "Non, toujours refuser \"{rule}\" (cette session)" msgid "No, always reject this tool" msgstr "Non, toujours refuser cet outil" -#: src/iac_code/ui/repl.py:374 +#: src/iac_code/ui/repl.py:406 msgid "Press Ctrl+C again to exit." msgstr "Appuyez de nouveau sur Ctrl+C pour quitter." -#: src/iac_code/ui/repl.py:399 +#: src/iac_code/ui/repl.py:431 msgid "Interrupted." msgstr "Interrompu." -#: src/iac_code/ui/repl.py:436 +#: src/iac_code/ui/repl.py:468 msgid "Goodbye!" msgstr "Au revoir !" -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:469 msgid "Resume this session with:" msgstr "Pour reprendre cette session :" -#: src/iac_code/ui/repl.py:462 +#: src/iac_code/ui/repl.py:494 msgid "Update now" msgstr "Mettre à jour maintenant" -#: src/iac_code/ui/repl.py:464 +#: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." msgstr "Exécute la commande de mise à jour affichée et quitte en cas de succès." -#: src/iac_code/ui/repl.py:467 +#: src/iac_code/ui/repl.py:499 msgid "Skip" msgstr "Ignorer" -#: src/iac_code/ui/repl.py:469 +#: src/iac_code/ui/repl.py:501 msgid "Continue with the current version for this session." msgstr "Continuer avec la version actuelle pour cette session." -#: src/iac_code/ui/repl.py:472 +#: src/iac_code/ui/repl.py:504 msgid "Skip until next version" msgstr "Ignorer jusqu’à la prochaine version" -#: src/iac_code/ui/repl.py:474 +#: src/iac_code/ui/repl.py:506 msgid "Hide this update until a newer version is available." msgstr "" "Masquer cette mise à jour jusqu’à ce qu’une version plus récente soit " "disponible." -#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 +#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." msgstr "La commande de mise à jour a échoué. La version actuelle sera conservée." -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:530 msgid "Update completed. Restart iac-code to continue." msgstr "Mise à jour terminée. Redémarrez iac-code pour continuer." -#: src/iac_code/ui/repl.py:536 +#: src/iac_code/ui/repl.py:568 msgid "No image in clipboard." msgstr "Aucune image dans le presse-papiers." -#: src/iac_code/ui/repl.py:722 +#: src/iac_code/ui/repl.py:754 msgid "Usage: !" msgstr "Utilisation : !" -#: src/iac_code/ui/repl.py:727 +#: src/iac_code/ui/repl.py:759 msgid "Shell command support is unavailable." msgstr "La prise en charge des commandes shell n'est pas disponible." -#: src/iac_code/ui/repl.py:791 +#: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Compétence inconnue : ${name}. Tapez / pour lister les commandes et les " "compétences." -#: src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available commands.Commande " "inconnue : /{name}. Saisissez /help pour la liste des commandes." -#: src/iac_code/ui/repl.py:798 +#: src/iac_code/ui/repl.py:833 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ n'invoque que des compétences. Utilisez plutôt /{name}." -#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 +#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 #, python-brace-format msgid "Command error: {error}" msgstr "Erreur de commande : {error}" -#: src/iac_code/ui/repl.py:827 +#: src/iac_code/ui/repl.py:862 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Aucun gestionnaire pour la commande : {name}" -#: src/iac_code/ui/repl.py:1132 +#: src/iac_code/ui/repl.py:1167 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Session introuvable : {session_id}" -#: src/iac_code/ui/repl.py:1151 +#: src/iac_code/ui/repl.py:1186 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2157,19 +2183,19 @@ msgstr "" "Pour la reprendre, exécutez :\n" " {cmd}" -#: src/iac_code/ui/repl.py:1252 +#: src/iac_code/ui/repl.py:1287 msgid "This conversation is from a different directory." msgstr "Cette conversation provient d’un autre répertoire." -#: src/iac_code/ui/repl.py:1254 +#: src/iac_code/ui/repl.py:1289 msgid "To resume, run:" msgstr "Pour reprendre, exécutez :" -#: src/iac_code/ui/repl.py:1259 +#: src/iac_code/ui/repl.py:1294 msgid "(Command copied to clipboard)" msgstr "(Commande copiée dans le presse-papiers)" -#: src/iac_code/ui/repl.py:1416 +#: src/iac_code/ui/repl.py:1451 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2178,12 +2204,12 @@ msgstr "" "Le modèle actuel {model} ne prend pas en charge l’entrée d’image. " "Utilisez /model pour passer à un modèle compatible vision." -#: src/iac_code/ui/repl.py:1425 +#: src/iac_code/ui/repl.py:1460 #, python-brace-format msgid "Image error: {err}" msgstr "Erreur d’image : {err}" -#: src/iac_code/ui/repl.py:1442 +#: src/iac_code/ui/repl.py:1477 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2364,6 +2390,91 @@ msgstr "il y a {n} heure{s}" msgid "{n} day{s} ago" msgstr "il y a {n} jour{s}" +#: src/iac_code/ui/dialogs/skills_picker.py:52 +msgid "Search skills..." +msgstr "Rechercher des compétences..." + +#: src/iac_code/ui/dialogs/skills_picker.py:159 +msgid "Skills" +msgstr "Compétences" + +#: src/iac_code/ui/dialogs/skills_picker.py:161 +#, python-brace-format +msgid "{current} of {total}" +msgstr "{current} sur {total}" + +#: src/iac_code/ui/dialogs/skills_picker.py:165 +#, python-brace-format +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "" +"{count} compétences - Espace pour activer/désactiver, Entrée pour " +"enregistrer, Tab pour trier, Échap pour annuler" + +#: src/iac_code/ui/dialogs/skills_picker.py:171 +#, python-brace-format +msgid "Sort: {mode}" +msgstr "Tri : {mode}" + +#: src/iac_code/ui/dialogs/skills_picker.py:176 +msgid "No skills found" +msgstr "Aucune compétence trouvée" + +#: src/iac_code/ui/dialogs/skills_picker.py:245 +msgid "Bundled skills cannot be disabled." +msgstr "Les compétences intégrées ne peuvent pas être désactivées." + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "on" +msgstr "activée" + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "off" +msgstr "désactivée" + +#: src/iac_code/ui/dialogs/skills_picker.py:265 +msgid "locked" +msgstr "verrouillée" + +#: src/iac_code/ui/dialogs/skills_picker.py:268 +msgid "matched description" +msgstr "correspond à la description" + +#: src/iac_code/ui/dialogs/skills_picker.py:277 +msgid "source" +msgstr "source" + +#: src/iac_code/ui/dialogs/skills_picker.py:279 +msgid "size" +msgstr "taille" + +#: src/iac_code/ui/dialogs/skills_picker.py:280 +msgid "name" +msgstr "nom" + +#: src/iac_code/ui/dialogs/skills_picker.py:285 +msgid "bundled" +msgstr "intégrée" + +#: src/iac_code/ui/dialogs/skills_picker.py:287 +msgid "project" +msgstr "projet" + +#: src/iac_code/ui/dialogs/skills_picker.py:289 +msgid "user" +msgstr "utilisateur" + +#: src/iac_code/ui/dialogs/skills_picker.py:296 +#, python-brace-format +msgid "~{count}k tokens" +msgstr "~{count}k jetons" + +#: src/iac_code/ui/dialogs/skills_picker.py:297 +#, python-brace-format +msgid "~{count} tokens" +msgstr "~{count} jetons" + #: src/iac_code/ui/suggestions/command_provider.py:79 msgid "Search saved memories" msgstr "Rechercher dans les mémoires enregistrées" @@ -2438,3 +2549,13 @@ msgstr "" #~ msgid "Cache create" #~ msgstr "Création du cache" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "" +#~ "{count} compétences - Espace pour " +#~ "activer/désactiver, Entrée pour enregistrer, /" +#~ " pour rechercher, t pour trier, Échap" +#~ " pour annuler" + 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 0a7da05..9d7ef47 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:13+0800\n" +"POT-Creation-Date: 2026-06-02 20:45+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: ja\n" @@ -106,8 +106,8 @@ msgstr "使用方法:/debug [on|off]" msgid "Memory manager is unavailable." msgstr "メモリマネージャーを利用できません。" -#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 -#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "権限が拒否されました。" @@ -587,59 +587,63 @@ msgstr "永続化された A2A ルート用のディレクトリ" msgid "Save the provided routes as a route snapshot" msgstr "指定されたルートをルートスナップショットとして保存します" -#: src/iac_code/commands/__init__.py:24 +#: src/iac_code/commands/__init__.py:25 msgid "Show available commands" msgstr "利用可能なコマンドを表示します" -#: src/iac_code/commands/__init__.py:33 +#: src/iac_code/commands/__init__.py:34 msgid "Clear conversation history" msgstr "会話履歴をクリアします" -#: src/iac_code/commands/__init__.py:41 +#: src/iac_code/commands/__init__.py:42 msgid "Show or switch model" msgstr "モデルを表示または切り替えます" -#: src/iac_code/commands/__init__.py:50 +#: src/iac_code/commands/__init__.py:51 msgid "Show or switch thinking effort" msgstr "思考の負荷(effort)を表示または切り替えます" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compact conversation context" msgstr "会話コンテキストを圧縮します" -#: src/iac_code/commands/__init__.py:61 +#: src/iac_code/commands/__init__.py:62 msgid "Compacting conversation" msgstr "会話を圧縮しています" -#: src/iac_code/commands/__init__.py:68 +#: src/iac_code/commands/__init__.py:69 msgid "Exit the application" msgstr "アプリケーションを終了します" -#: src/iac_code/commands/__init__.py:77 +#: src/iac_code/commands/__init__.py:78 msgid "Authenticate with LLM provider" msgstr "LLM プロバイダーで認証を行います" -#: src/iac_code/commands/__init__.py:86 +#: src/iac_code/commands/__init__.py:87 msgid "Toggle debug logging" msgstr "デバッグログのオン/オフを切り替えます" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:96 msgid "View and manage persistent memories" msgstr "永続メモリを表示および管理" -#: src/iac_code/commands/__init__.py:97 +#: src/iac_code/commands/__init__.py:98 msgid "[|search |delete |help]" msgstr "[<名前>|search <検索語>|delete <名前>|help]" -#: src/iac_code/commands/__init__.py:104 +#: src/iac_code/commands/__init__.py:105 msgid "Resume a previous session" msgstr "以前のセッションを再開します" -#: src/iac_code/commands/__init__.py:106 +#: src/iac_code/commands/__init__.py:107 msgid "[conversation id or search term]" msgstr "[会話 ID または検索語]" -#: src/iac_code/commands/__init__.py:113 +#: src/iac_code/commands/__init__.py:114 +msgid "Manage skills" +msgstr "スキルを管理" + +#: src/iac_code/commands/__init__.py:122 msgid "Show current session status" msgstr "現在のセッション状態を表示" @@ -822,7 +826,7 @@ msgid "Credential" msgstr "クレデンシャル" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "リージョン" @@ -1032,81 +1036,93 @@ msgstr "セッションが見つかりません:{arg}" msgid "Resume cancelled" msgstr "resume をキャンセルしました" -#: src/iac_code/commands/status.py:16 +#: src/iac_code/commands/skills.py:14 +msgid "Skills management is only available in interactive mode." +msgstr "スキル管理は対話モードでのみ使用できます。" + +#: src/iac_code/commands/skills.py:25 +msgid "Skills update cancelled" +msgstr "スキル更新をキャンセルしました" + +#: src/iac_code/commands/skills.py:29 +msgid "Skills updated" +msgstr "スキルを更新しました" + +#: src/iac_code/commands/status.py:19 msgid "Status command requires a context." msgstr "status コマンドにはコンテキストが必要です。" -#: src/iac_code/commands/status.py:19 +#: src/iac_code/commands/status.py:22 msgid "Status command requires a REPL context." msgstr "status コマンドには REPL コンテキストが必要です。" -#: src/iac_code/commands/status.py:21 +#: src/iac_code/commands/status.py:24 msgid "Status is only available in interactive mode." msgstr "status は対話モードでのみ利用できます。" -#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 msgid "Session" msgstr "セッション" -#: src/iac_code/commands/status.py:31 +#: src/iac_code/commands/status.py:34 msgid "Provider" msgstr "プロバイダー" -#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 -#: src/iac_code/commands/status.py:33 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "未設定" -#: src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:35 msgid "Model" msgstr "モデル" -#: src/iac_code/commands/status.py:34 +#: src/iac_code/commands/status.py:37 msgid "CWD" msgstr "現在のディレクトリ" -#: src/iac_code/commands/status.py:38 +#: src/iac_code/commands/status.py:41 msgid "API Token Usage (recorded):" msgstr "API トークン使用量(記録済み):" -#: src/iac_code/commands/status.py:41 +#: src/iac_code/commands/status.py:44 msgid "Input" msgstr "入力" -#: src/iac_code/commands/status.py:42 +#: src/iac_code/commands/status.py:45 msgid "Output" msgstr "出力" -#: src/iac_code/commands/status.py:43 +#: src/iac_code/commands/status.py:46 msgid "Cache read" msgstr "キャッシュ読み取り" -#: src/iac_code/commands/status.py:44 +#: src/iac_code/commands/status.py:47 msgid "Total" msgstr "合計" -#: src/iac_code/commands/status.py:47 +#: src/iac_code/commands/status.py:50 msgid "No recorded API usage for this session yet." msgstr "このセッションにはまだ記録済みの API 使用量がありません。" -#: src/iac_code/commands/status.py:51 +#: src/iac_code/commands/status.py:54 msgid "Turns" msgstr "ターン" -#: src/iac_code/commands/status.py:52 +#: src/iac_code/commands/status.py:55 msgid "Context" msgstr "コンテキスト" -#: src/iac_code/commands/status.py:54 +#: src/iac_code/commands/status.py:57 msgid "Session Status" msgstr "セッション状態" -#: src/iac_code/commands/status.py:68 +#: src/iac_code/commands/status.py:73 #, python-brace-format msgid "{session_id} (resumed)" msgstr "{session_id}(再開済み)" -#: src/iac_code/commands/status.py:76 +#: src/iac_code/commands/status.py:81 #, python-brace-format msgid "{percent} used ({total} / {window})" msgstr "{percent} 使用済み({total} / {window})" @@ -1291,15 +1307,25 @@ msgstr "無効な --permission-mode {!r} です。有効な値: {}" msgid "Allow {}?" msgstr "{} を許可しますか?" -#: src/iac_code/skills/skill_tool.py:130 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#, python-brace-format +msgid "Skill '{name}' is disabled. Run /skills to enable it." +msgstr "スキル「{name}」は無効です。有効にするには /skills を実行してください。" + +#: src/iac_code/skills/skill_tool.py:137 #, python-brace-format msgid "Skill '{name}' loaded (inline)." msgstr "スキル '{name}' を読み込みました(インライン)。" -#: src/iac_code/skills/skill_tool.py:214 +#: src/iac_code/skills/skill_tool.py:221 msgid "Skill" msgstr "スキル" +#: src/iac_code/skills/skill_tool.py:245 +#, python-brace-format +msgid "Skill disabled: {name}" +msgstr "スキルが無効です: {name}" + #: src/iac_code/skills/bundled/simplify.py:25 msgid "" "Review changed code for reuse, quality, and efficiency, then fix issues " @@ -1995,99 +2021,99 @@ msgstr "いいえ、常に \"{rule}\" を拒否(このセッション)" msgid "No, always reject this tool" msgstr "いいえ、このツールは常に拒否" -#: src/iac_code/ui/repl.py:374 +#: src/iac_code/ui/repl.py:406 msgid "Press Ctrl+C again to exit." msgstr "終了するには Ctrl+C をもう一度押してください。" -#: src/iac_code/ui/repl.py:399 +#: src/iac_code/ui/repl.py:431 msgid "Interrupted." msgstr "中断しました。" -#: src/iac_code/ui/repl.py:436 +#: src/iac_code/ui/repl.py:468 msgid "Goodbye!" msgstr "さようなら。" -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:469 msgid "Resume this session with:" msgstr "このセッションを再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:462 +#: src/iac_code/ui/repl.py:494 msgid "Update now" msgstr "今すぐ更新" -#: src/iac_code/ui/repl.py:464 +#: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." msgstr "表示された更新コマンドを実行し、成功したら終了します。" -#: src/iac_code/ui/repl.py:467 +#: src/iac_code/ui/repl.py:499 msgid "Skip" msgstr "スキップ" -#: src/iac_code/ui/repl.py:469 +#: src/iac_code/ui/repl.py:501 msgid "Continue with the current version for this session." msgstr "このセッションでは現在のバージョンを使い続けます。" -#: src/iac_code/ui/repl.py:472 +#: src/iac_code/ui/repl.py:504 msgid "Skip until next version" msgstr "次のバージョンまでスキップ" -#: src/iac_code/ui/repl.py:474 +#: src/iac_code/ui/repl.py:506 msgid "Hide this update until a newer version is available." msgstr "より新しいバージョンが利用可能になるまで、この更新を非表示にします。" -#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 +#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." msgstr "更新コマンドに失敗しました。現在のバージョンで続行します。" -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:530 msgid "Update completed. Restart iac-code to continue." msgstr "更新が完了しました。続行するには iac-code を再起動してください。" -#: src/iac_code/ui/repl.py:536 +#: src/iac_code/ui/repl.py:568 msgid "No image in clipboard." msgstr "クリップボードに画像がありません。" -#: src/iac_code/ui/repl.py:722 +#: src/iac_code/ui/repl.py:754 msgid "Usage: !" msgstr "使用方法: !" -#: src/iac_code/ui/repl.py:727 +#: src/iac_code/ui/repl.py:759 msgid "Shell command support is unavailable." msgstr "シェルコマンドのサポートは利用できません。" -#: src/iac_code/ui/repl.py:791 +#: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "不明なスキル: ${name}。/ を入力するとコマンドとスキルを一覧表示します。" -#: src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available " "commands.不明なコマンドです:/{name}。利用可能なコマンドは /help を入力してください。" -#: src/iac_code/ui/repl.py:798 +#: src/iac_code/ui/repl.py:833 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ はスキルのみを呼び出します。代わりに /{name} を使用してください。" -#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 +#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 #, python-brace-format msgid "Command error: {error}" msgstr "コマンドエラー:{error}" -#: src/iac_code/ui/repl.py:827 +#: src/iac_code/ui/repl.py:862 #, python-brace-format msgid "Command has no handler: {name}" msgstr "ハンドラーがないコマンドです:{name}" -#: src/iac_code/ui/repl.py:1132 +#: src/iac_code/ui/repl.py:1167 #, python-brace-format msgid "Session not found: {session_id}" msgstr "セッションが見つかりません:{session_id}" -#: src/iac_code/ui/repl.py:1151 +#: src/iac_code/ui/repl.py:1186 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2098,31 +2124,31 @@ msgstr "" "再開するには次を実行してください:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1252 +#: src/iac_code/ui/repl.py:1287 msgid "This conversation is from a different directory." msgstr "この会話は別のディレクトリ由来です。" -#: src/iac_code/ui/repl.py:1254 +#: src/iac_code/ui/repl.py:1289 msgid "To resume, run:" msgstr "再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:1259 +#: src/iac_code/ui/repl.py:1294 msgid "(Command copied to clipboard)" msgstr "(コマンドをクリップボードにコピーしました)" -#: src/iac_code/ui/repl.py:1416 +#: src/iac_code/ui/repl.py:1451 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " "to a vision-capable model." msgstr "現在のモデル {model} は画像入力をサポートしていません。/model を使用してビジョン対応モデルに切り替えてください。" -#: src/iac_code/ui/repl.py:1425 +#: src/iac_code/ui/repl.py:1460 #, python-brace-format msgid "Image error: {err}" msgstr "画像エラー:{err}" -#: src/iac_code/ui/repl.py:1442 +#: src/iac_code/ui/repl.py:1477 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2301,6 +2327,89 @@ msgstr "{n} 時間{s}前" msgid "{n} day{s} ago" msgstr "{n} 日{s}前" +#: src/iac_code/ui/dialogs/skills_picker.py:52 +msgid "Search skills..." +msgstr "スキルを検索..." + +#: src/iac_code/ui/dialogs/skills_picker.py:159 +msgid "Skills" +msgstr "スキル" + +#: src/iac_code/ui/dialogs/skills_picker.py:161 +#, python-brace-format +msgid "{current} of {total}" +msgstr "{total} 件中 {current}" + +#: src/iac_code/ui/dialogs/skills_picker.py:165 +#, python-brace-format +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "{count} 個のスキル - Space で切り替え、Enter で保存、Tab で並べ替え、Esc でキャンセル" + +#: src/iac_code/ui/dialogs/skills_picker.py:171 +#, python-brace-format +msgid "Sort: {mode}" +msgstr "並べ替え: {mode}" + +#: src/iac_code/ui/dialogs/skills_picker.py:176 +msgid "No skills found" +msgstr "スキルが見つかりません" + +#: src/iac_code/ui/dialogs/skills_picker.py:245 +msgid "Bundled skills cannot be disabled." +msgstr "バンドルスキルは無効にできません。" + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "on" +msgstr "有効" + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "off" +msgstr "無効" + +#: src/iac_code/ui/dialogs/skills_picker.py:265 +msgid "locked" +msgstr "ロック済み" + +#: src/iac_code/ui/dialogs/skills_picker.py:268 +msgid "matched description" +msgstr "説明に一致" + +#: src/iac_code/ui/dialogs/skills_picker.py:277 +msgid "source" +msgstr "提供元" + +#: src/iac_code/ui/dialogs/skills_picker.py:279 +msgid "size" +msgstr "サイズ" + +#: src/iac_code/ui/dialogs/skills_picker.py:280 +msgid "name" +msgstr "名前" + +#: src/iac_code/ui/dialogs/skills_picker.py:285 +msgid "bundled" +msgstr "バンドル" + +#: src/iac_code/ui/dialogs/skills_picker.py:287 +msgid "project" +msgstr "プロジェクト" + +#: src/iac_code/ui/dialogs/skills_picker.py:289 +msgid "user" +msgstr "ユーザー" + +#: src/iac_code/ui/dialogs/skills_picker.py:296 +#, python-brace-format +msgid "~{count}k tokens" +msgstr "約{count}kトークン" + +#: src/iac_code/ui/dialogs/skills_picker.py:297 +#, python-brace-format +msgid "~{count} tokens" +msgstr "約{count}トークン" + #: src/iac_code/ui/suggestions/command_provider.py:79 msgid "Search saved memories" msgstr "保存済みメモリを検索" @@ -2385,3 +2494,9 @@ msgstr " オプション 2 - github.com にアクセスできない場合は、 #~ msgid "Cache create" #~ msgstr "キャッシュ作成" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "{count} 個のスキル - Space で切り替え、Enter で保存、/ で検索、t で並べ替え、Esc でキャンセル" + 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 29be64a..08b552b 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:13+0800\n" +"POT-Creation-Date: 2026-06-02 20:45+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: pt\n" @@ -112,8 +112,8 @@ msgstr "Uso: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "O gerenciador de memória está indisponível." -#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 -#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "Permissão negada." @@ -607,59 +607,63 @@ msgstr "Diretório para rotas A2A persistidas" msgid "Save the provided routes as a route snapshot" msgstr "Salva as rotas fornecidas como um snapshot de rotas" -#: src/iac_code/commands/__init__.py:24 +#: src/iac_code/commands/__init__.py:25 msgid "Show available commands" msgstr "Mostrar comandos disponíveis" -#: src/iac_code/commands/__init__.py:33 +#: src/iac_code/commands/__init__.py:34 msgid "Clear conversation history" msgstr "Limpar histórico da conversa" -#: src/iac_code/commands/__init__.py:41 +#: src/iac_code/commands/__init__.py:42 msgid "Show or switch model" msgstr "Exibir ou trocar modelo" -#: src/iac_code/commands/__init__.py:50 +#: src/iac_code/commands/__init__.py:51 msgid "Show or switch thinking effort" msgstr "Exibir ou alterar o nível de esforço de raciocínio" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compact conversation context" msgstr "Compactar contexto da conversa" -#: src/iac_code/commands/__init__.py:61 +#: src/iac_code/commands/__init__.py:62 msgid "Compacting conversation" msgstr "Compactando conversa" -#: src/iac_code/commands/__init__.py:68 +#: src/iac_code/commands/__init__.py:69 msgid "Exit the application" msgstr "Encerrar o aplicativo" -#: src/iac_code/commands/__init__.py:77 +#: src/iac_code/commands/__init__.py:78 msgid "Authenticate with LLM provider" msgstr "Autenticar com o provedor LLM" -#: src/iac_code/commands/__init__.py:86 +#: src/iac_code/commands/__init__.py:87 msgid "Toggle debug logging" msgstr "Alternar debug" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:96 msgid "View and manage persistent memories" msgstr "Ver e gerenciar memórias persistentes" -#: src/iac_code/commands/__init__.py:97 +#: src/iac_code/commands/__init__.py:98 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:104 +#: src/iac_code/commands/__init__.py:105 msgid "Resume a previous session" msgstr "Retomar uma sessão anterior" -#: src/iac_code/commands/__init__.py:106 +#: src/iac_code/commands/__init__.py:107 msgid "[conversation id or search term]" msgstr "[ID da conversa ou termo de busca]" -#: src/iac_code/commands/__init__.py:113 +#: src/iac_code/commands/__init__.py:114 +msgid "Manage skills" +msgstr "Gerenciar habilidades" + +#: src/iac_code/commands/__init__.py:122 msgid "Show current session status" msgstr "Mostrar o status atual da sessão" @@ -842,7 +846,7 @@ msgid "Credential" msgstr "Credencial" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Região" @@ -1050,81 +1054,93 @@ msgstr "Sessão não encontrada: {arg}" msgid "Resume cancelled" msgstr "Retomada cancelada" -#: src/iac_code/commands/status.py:16 +#: src/iac_code/commands/skills.py:14 +msgid "Skills management is only available in interactive mode." +msgstr "O gerenciamento de habilidades só está disponível no modo interativo." + +#: src/iac_code/commands/skills.py:25 +msgid "Skills update cancelled" +msgstr "Atualização de habilidades cancelada" + +#: src/iac_code/commands/skills.py:29 +msgid "Skills updated" +msgstr "Habilidades atualizadas" + +#: src/iac_code/commands/status.py:19 msgid "Status command requires a context." msgstr "O comando status requer um contexto." -#: src/iac_code/commands/status.py:19 +#: src/iac_code/commands/status.py:22 msgid "Status command requires a REPL context." msgstr "O comando status requer um contexto REPL." -#: src/iac_code/commands/status.py:21 +#: src/iac_code/commands/status.py:24 msgid "Status is only available in interactive mode." msgstr "status está disponível apenas no modo interativo." -#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 msgid "Session" msgstr "Sessão" -#: src/iac_code/commands/status.py:31 +#: src/iac_code/commands/status.py:34 msgid "Provider" msgstr "Provedor" -#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 -#: src/iac_code/commands/status.py:33 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "não configurado" -#: src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:35 msgid "Model" msgstr "Modelo" -#: src/iac_code/commands/status.py:34 +#: src/iac_code/commands/status.py:37 msgid "CWD" msgstr "Diretório atual" -#: src/iac_code/commands/status.py:38 +#: src/iac_code/commands/status.py:41 msgid "API Token Usage (recorded):" msgstr "Uso de tokens da API (registrado):" -#: src/iac_code/commands/status.py:41 +#: src/iac_code/commands/status.py:44 msgid "Input" msgstr "Entrada" -#: src/iac_code/commands/status.py:42 +#: src/iac_code/commands/status.py:45 msgid "Output" msgstr "Saída" -#: src/iac_code/commands/status.py:43 +#: src/iac_code/commands/status.py:46 msgid "Cache read" msgstr "Leitura de cache" -#: src/iac_code/commands/status.py:44 +#: src/iac_code/commands/status.py:47 msgid "Total" msgstr "Total" -#: src/iac_code/commands/status.py:47 +#: src/iac_code/commands/status.py:50 msgid "No recorded API usage for this session yet." msgstr "Ainda não há uso de API registrado para esta sessão." -#: src/iac_code/commands/status.py:51 +#: src/iac_code/commands/status.py:54 msgid "Turns" msgstr "Turnos" -#: src/iac_code/commands/status.py:52 +#: src/iac_code/commands/status.py:55 msgid "Context" msgstr "Contexto" -#: src/iac_code/commands/status.py:54 +#: src/iac_code/commands/status.py:57 msgid "Session Status" msgstr "Status da sessão" -#: src/iac_code/commands/status.py:68 +#: src/iac_code/commands/status.py:73 #, python-brace-format msgid "{session_id} (resumed)" msgstr "{session_id} (retomada)" -#: src/iac_code/commands/status.py:76 +#: src/iac_code/commands/status.py:81 #, python-brace-format msgid "{percent} used ({total} / {window})" msgstr "{percent} usado ({total} / {window})" @@ -1315,15 +1331,25 @@ msgstr "--permission-mode inválido {!r}. Valores válidos: {}" msgid "Allow {}?" msgstr "Permitir {}?" -#: src/iac_code/skills/skill_tool.py:130 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#, python-brace-format +msgid "Skill '{name}' is disabled. Run /skills to enable it." +msgstr "A habilidade '{name}' está desativada. Execute /skills para ativá-la." + +#: src/iac_code/skills/skill_tool.py:137 #, python-brace-format msgid "Skill '{name}' loaded (inline)." msgstr "Skill '{name}' carregada (inline)." -#: src/iac_code/skills/skill_tool.py:214 +#: src/iac_code/skills/skill_tool.py:221 msgid "Skill" msgstr "Skill" +#: src/iac_code/skills/skill_tool.py:245 +#, python-brace-format +msgid "Skill disabled: {name}" +msgstr "Habilidade desativada: {name}" + #: src/iac_code/skills/bundled/simplify.py:25 msgid "" "Review changed code for reuse, quality, and efficiency, then fix issues " @@ -2031,101 +2057,101 @@ msgstr "Não, sempre negar \"{rule}\" (esta sessão)" msgid "No, always reject this tool" msgstr "Não, sempre rejeitar esta ferramenta" -#: src/iac_code/ui/repl.py:374 +#: src/iac_code/ui/repl.py:406 msgid "Press Ctrl+C again to exit." msgstr "Pressione Ctrl+C novamente para sair." -#: src/iac_code/ui/repl.py:399 +#: src/iac_code/ui/repl.py:431 msgid "Interrupted." msgstr "Interrompido." -#: src/iac_code/ui/repl.py:436 +#: src/iac_code/ui/repl.py:468 msgid "Goodbye!" msgstr "Até logo!" -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:469 msgid "Resume this session with:" msgstr "Para retomar esta sessão, execute:" -#: src/iac_code/ui/repl.py:462 +#: src/iac_code/ui/repl.py:494 msgid "Update now" msgstr "Atualizar agora" -#: src/iac_code/ui/repl.py:464 +#: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Executa o comando de atualização mostrado e sai quando ele for concluído " "com sucesso." -#: src/iac_code/ui/repl.py:467 +#: src/iac_code/ui/repl.py:499 msgid "Skip" msgstr "Ignorar" -#: src/iac_code/ui/repl.py:469 +#: src/iac_code/ui/repl.py:501 msgid "Continue with the current version for this session." msgstr "Continuar com a versão atual nesta sessão." -#: src/iac_code/ui/repl.py:472 +#: src/iac_code/ui/repl.py:504 msgid "Skip until next version" msgstr "Ignorar até a próxima versão" -#: src/iac_code/ui/repl.py:474 +#: src/iac_code/ui/repl.py:506 msgid "Hide this update until a newer version is available." msgstr "Ocultar esta atualização até que uma versão mais nova esteja disponível." -#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 +#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." msgstr "O comando de atualização falhou. Continuando com a versão atual." -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:530 msgid "Update completed. Restart iac-code to continue." msgstr "Atualização concluída. Reinicie o iac-code para continuar." -#: src/iac_code/ui/repl.py:536 +#: src/iac_code/ui/repl.py:568 msgid "No image in clipboard." msgstr "Nenhuma imagem na área de transferência." -#: src/iac_code/ui/repl.py:722 +#: src/iac_code/ui/repl.py:754 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:727 +#: src/iac_code/ui/repl.py:759 msgid "Shell command support is unavailable." msgstr "O suporte a comandos shell não está disponível." -#: src/iac_code/ui/repl.py:791 +#: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Habilidade desconhecida: ${name}. Digite / para listar comandos e " "habilidades." -#: src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "Comando desconhecido: /{name}. Digite /help para ver os comandos." -#: src/iac_code/ui/repl.py:798 +#: src/iac_code/ui/repl.py:833 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ invoca apenas habilidades. Use /{name} em vez disso." -#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 +#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 #, python-brace-format msgid "Command error: {error}" msgstr "Erro de comando: {error}" -#: src/iac_code/ui/repl.py:827 +#: src/iac_code/ui/repl.py:862 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Comando sem tratador: {name}" -#: src/iac_code/ui/repl.py:1132 +#: src/iac_code/ui/repl.py:1167 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sessão não encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1151 +#: src/iac_code/ui/repl.py:1186 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2136,19 +2162,19 @@ msgstr "" "Para retomar, execute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1252 +#: src/iac_code/ui/repl.py:1287 msgid "This conversation is from a different directory." msgstr "Esta conversa é de outro diretório." -#: src/iac_code/ui/repl.py:1254 +#: src/iac_code/ui/repl.py:1289 msgid "To resume, run:" msgstr "Para retomar, execute:" -#: src/iac_code/ui/repl.py:1259 +#: src/iac_code/ui/repl.py:1294 msgid "(Command copied to clipboard)" msgstr "(Comando copiado para a área de transferência)" -#: src/iac_code/ui/repl.py:1416 +#: src/iac_code/ui/repl.py:1451 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2157,12 +2183,12 @@ msgstr "" "O modelo atual {model} não suporta entrada de imagem. Use /model para " "alternar para um modelo com capacidade de visão." -#: src/iac_code/ui/repl.py:1425 +#: src/iac_code/ui/repl.py:1460 #, python-brace-format msgid "Image error: {err}" msgstr "Erro de imagem: {err}" -#: src/iac_code/ui/repl.py:1442 +#: src/iac_code/ui/repl.py:1477 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2343,6 +2369,91 @@ msgstr "há {n} hora{s}" msgid "{n} day{s} ago" msgstr "há {n} dia{s}" +#: src/iac_code/ui/dialogs/skills_picker.py:52 +msgid "Search skills..." +msgstr "Buscar habilidades..." + +#: src/iac_code/ui/dialogs/skills_picker.py:159 +msgid "Skills" +msgstr "Habilidades" + +#: src/iac_code/ui/dialogs/skills_picker.py:161 +#, python-brace-format +msgid "{current} of {total}" +msgstr "{current} de {total}" + +#: src/iac_code/ui/dialogs/skills_picker.py:165 +#, python-brace-format +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "" +"{count} habilidades - Espaço para alternar, Enter para salvar, Tab para " +"ordenar, Esc para cancelar" + +#: src/iac_code/ui/dialogs/skills_picker.py:171 +#, python-brace-format +msgid "Sort: {mode}" +msgstr "Ordenar: {mode}" + +#: src/iac_code/ui/dialogs/skills_picker.py:176 +msgid "No skills found" +msgstr "Nenhuma habilidade encontrada" + +#: src/iac_code/ui/dialogs/skills_picker.py:245 +msgid "Bundled skills cannot be disabled." +msgstr "Habilidades integradas não podem ser desativadas." + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "on" +msgstr "ativada" + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "off" +msgstr "desativada" + +#: src/iac_code/ui/dialogs/skills_picker.py:265 +msgid "locked" +msgstr "bloqueada" + +#: src/iac_code/ui/dialogs/skills_picker.py:268 +msgid "matched description" +msgstr "corresponde à descrição" + +#: src/iac_code/ui/dialogs/skills_picker.py:277 +msgid "source" +msgstr "origem" + +#: src/iac_code/ui/dialogs/skills_picker.py:279 +msgid "size" +msgstr "tamanho" + +#: src/iac_code/ui/dialogs/skills_picker.py:280 +msgid "name" +msgstr "nome" + +#: src/iac_code/ui/dialogs/skills_picker.py:285 +msgid "bundled" +msgstr "integrada" + +#: src/iac_code/ui/dialogs/skills_picker.py:287 +msgid "project" +msgstr "projeto" + +#: src/iac_code/ui/dialogs/skills_picker.py:289 +msgid "user" +msgstr "usuário" + +#: src/iac_code/ui/dialogs/skills_picker.py:296 +#, python-brace-format +msgid "~{count}k tokens" +msgstr "~{count}k tokens" + +#: src/iac_code/ui/dialogs/skills_picker.py:297 +#, python-brace-format +msgid "~{count} tokens" +msgstr "~{count} tokens" + #: src/iac_code/ui/suggestions/command_provider.py:79 msgid "Search saved memories" msgstr "Pesquisar memórias salvas" @@ -2417,3 +2528,13 @@ msgstr "" #~ msgid "Cache create" #~ msgstr "Criação de cache" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "" +#~ "{count} habilidades - Espaço para " +#~ "alternar, Enter para salvar, / para " +#~ "buscar, t para ordenar, Esc para " +#~ "cancelar" + 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 93b7336..a9fec79 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:13+0800\n" +"POT-Creation-Date: 2026-06-02 20:45+0800\n" "PO-Revision-Date: 2026-04-02 00:00+0000\n" "Last-Translator: \n" "Language: zh\n" @@ -102,8 +102,8 @@ msgstr "用法:/debug [on|off]" msgid "Memory manager is unavailable." msgstr "记忆管理器不可用。" -#: src/iac_code/agent/agent_loop.py:409 src/iac_code/agent/agent_loop.py:424 -#: src/iac_code/ui/repl.py:761 src/iac_code/ui/repl.py:775 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "权限被拒绝。" @@ -579,59 +579,63 @@ msgstr "持久化 A2A 路由的目录" msgid "Save the provided routes as a route snapshot" msgstr "将提供的路由保存为路由快照" -#: src/iac_code/commands/__init__.py:24 +#: src/iac_code/commands/__init__.py:25 msgid "Show available commands" msgstr "显示可用命令" -#: src/iac_code/commands/__init__.py:33 +#: src/iac_code/commands/__init__.py:34 msgid "Clear conversation history" msgstr "清除对话历史" -#: src/iac_code/commands/__init__.py:41 +#: src/iac_code/commands/__init__.py:42 msgid "Show or switch model" msgstr "显示或切换模型" -#: src/iac_code/commands/__init__.py:50 +#: src/iac_code/commands/__init__.py:51 msgid "Show or switch thinking effort" msgstr "显示或切换思考强度" -#: src/iac_code/commands/__init__.py:59 +#: src/iac_code/commands/__init__.py:60 msgid "Compact conversation context" msgstr "压缩对话上下文" -#: src/iac_code/commands/__init__.py:61 +#: src/iac_code/commands/__init__.py:62 msgid "Compacting conversation" msgstr "正在压缩对话" -#: src/iac_code/commands/__init__.py:68 +#: src/iac_code/commands/__init__.py:69 msgid "Exit the application" msgstr "退出应用程序" -#: src/iac_code/commands/__init__.py:77 +#: src/iac_code/commands/__init__.py:78 msgid "Authenticate with LLM provider" msgstr "配置 LLM 提供商认证" -#: src/iac_code/commands/__init__.py:86 +#: src/iac_code/commands/__init__.py:87 msgid "Toggle debug logging" msgstr "切换调试日志开关" -#: src/iac_code/commands/__init__.py:95 +#: src/iac_code/commands/__init__.py:96 msgid "View and manage persistent memories" msgstr "查看和管理持久记忆" -#: src/iac_code/commands/__init__.py:97 +#: src/iac_code/commands/__init__.py:98 msgid "[|search |delete |help]" msgstr "[<名称>|search <查询>|delete <名称>|help]" -#: src/iac_code/commands/__init__.py:104 +#: src/iac_code/commands/__init__.py:105 msgid "Resume a previous session" msgstr "恢复之前的会话" -#: src/iac_code/commands/__init__.py:106 +#: src/iac_code/commands/__init__.py:107 msgid "[conversation id or search term]" msgstr "[会话 ID 或搜索词]" -#: src/iac_code/commands/__init__.py:113 +#: src/iac_code/commands/__init__.py:114 +msgid "Manage skills" +msgstr "管理技能" + +#: src/iac_code/commands/__init__.py:122 msgid "Show current session status" msgstr "显示当前会话状态" @@ -814,7 +818,7 @@ msgid "Credential" msgstr "凭证" #: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:33 +#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "地域" @@ -1020,81 +1024,93 @@ msgstr "未找到会话:{arg}" msgid "Resume cancelled" msgstr "已取消恢复" -#: src/iac_code/commands/status.py:16 +#: src/iac_code/commands/skills.py:14 +msgid "Skills management is only available in interactive mode." +msgstr "技能管理仅在交互模式下可用。" + +#: src/iac_code/commands/skills.py:25 +msgid "Skills update cancelled" +msgstr "已取消技能更新" + +#: src/iac_code/commands/skills.py:29 +msgid "Skills updated" +msgstr "技能已更新" + +#: src/iac_code/commands/status.py:19 msgid "Status command requires a context." msgstr "status 命令需要上下文。" -#: src/iac_code/commands/status.py:19 +#: src/iac_code/commands/status.py:22 msgid "Status command requires a REPL context." msgstr "status 命令需要 REPL 上下文。" -#: src/iac_code/commands/status.py:21 +#: src/iac_code/commands/status.py:24 msgid "Status is only available in interactive mode." msgstr "status 仅在交互模式下可用。" -#: src/iac_code/commands/status.py:30 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 msgid "Session" msgstr "会话" -#: src/iac_code/commands/status.py:31 +#: src/iac_code/commands/status.py:34 msgid "Provider" msgstr "提供商" -#: src/iac_code/commands/status.py:31 src/iac_code/commands/status.py:32 -#: src/iac_code/commands/status.py:33 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "未配置" -#: src/iac_code/commands/status.py:32 +#: src/iac_code/commands/status.py:35 msgid "Model" msgstr "模型" -#: src/iac_code/commands/status.py:34 +#: src/iac_code/commands/status.py:37 msgid "CWD" msgstr "当前目录" -#: src/iac_code/commands/status.py:38 +#: src/iac_code/commands/status.py:41 msgid "API Token Usage (recorded):" msgstr "API Token 用量(已记录):" -#: src/iac_code/commands/status.py:41 +#: src/iac_code/commands/status.py:44 msgid "Input" msgstr "输入" -#: src/iac_code/commands/status.py:42 +#: src/iac_code/commands/status.py:45 msgid "Output" msgstr "输出" -#: src/iac_code/commands/status.py:43 +#: src/iac_code/commands/status.py:46 msgid "Cache read" msgstr "缓存读取" -#: src/iac_code/commands/status.py:44 +#: src/iac_code/commands/status.py:47 msgid "Total" msgstr "总计" -#: src/iac_code/commands/status.py:47 +#: src/iac_code/commands/status.py:50 msgid "No recorded API usage for this session yet." msgstr "此会话尚无已记录的 API 用量。" -#: src/iac_code/commands/status.py:51 +#: src/iac_code/commands/status.py:54 msgid "Turns" msgstr "轮次" -#: src/iac_code/commands/status.py:52 +#: src/iac_code/commands/status.py:55 msgid "Context" msgstr "上下文" -#: src/iac_code/commands/status.py:54 +#: src/iac_code/commands/status.py:57 msgid "Session Status" msgstr "会话状态" -#: src/iac_code/commands/status.py:68 +#: src/iac_code/commands/status.py:73 #, python-brace-format msgid "{session_id} (resumed)" msgstr "{session_id}(已恢复)" -#: src/iac_code/commands/status.py:76 +#: src/iac_code/commands/status.py:81 #, python-brace-format msgid "{percent} used ({total} / {window})" msgstr "已使用 {percent}({total} / {window})" @@ -1276,15 +1292,25 @@ msgstr "无效的 --permission-mode {!r}。有效值:{}" msgid "Allow {}?" msgstr "允许 {}?" -#: src/iac_code/skills/skill_tool.py:130 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#, python-brace-format +msgid "Skill '{name}' is disabled. Run /skills to enable it." +msgstr "技能“{name}”已禁用。运行 /skills 以启用它。" + +#: src/iac_code/skills/skill_tool.py:137 #, python-brace-format msgid "Skill '{name}' loaded (inline)." msgstr "技能 '{name}' 已加载(内联)。" -#: src/iac_code/skills/skill_tool.py:214 +#: src/iac_code/skills/skill_tool.py:221 msgid "Skill" msgstr "技能" +#: src/iac_code/skills/skill_tool.py:245 +#, python-brace-format +msgid "Skill disabled: {name}" +msgstr "技能已禁用:{name}" + #: src/iac_code/skills/bundled/simplify.py:25 msgid "" "Review changed code for reuse, quality, and efficiency, then fix issues " @@ -1976,97 +2002,97 @@ msgstr "否,始终拒绝 \"{rule}\"(本次会话)" msgid "No, always reject this tool" msgstr "否,始终拒绝此工具" -#: src/iac_code/ui/repl.py:374 +#: src/iac_code/ui/repl.py:406 msgid "Press Ctrl+C again to exit." msgstr "再次按 Ctrl+C 退出。" -#: src/iac_code/ui/repl.py:399 +#: src/iac_code/ui/repl.py:431 msgid "Interrupted." msgstr "已中断。" -#: src/iac_code/ui/repl.py:436 +#: src/iac_code/ui/repl.py:468 msgid "Goodbye!" msgstr "再见!" -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:469 msgid "Resume this session with:" msgstr "恢复此会话请运行:" -#: src/iac_code/ui/repl.py:462 +#: src/iac_code/ui/repl.py:494 msgid "Update now" msgstr "立即更新" -#: src/iac_code/ui/repl.py:464 +#: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." msgstr "运行显示的更新命令,成功后退出。" -#: src/iac_code/ui/repl.py:467 +#: src/iac_code/ui/repl.py:499 msgid "Skip" msgstr "跳过" -#: src/iac_code/ui/repl.py:469 +#: src/iac_code/ui/repl.py:501 msgid "Continue with the current version for this session." msgstr "本次会话继续使用当前版本。" -#: src/iac_code/ui/repl.py:472 +#: src/iac_code/ui/repl.py:504 msgid "Skip until next version" msgstr "跳过直到下一个版本" -#: src/iac_code/ui/repl.py:474 +#: src/iac_code/ui/repl.py:506 msgid "Hide this update until a newer version is available." msgstr "隐藏此更新,直到有更新的版本可用。" -#: src/iac_code/ui/repl.py:493 src/iac_code/ui/repl.py:505 +#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." msgstr "更新命令失败。将继续使用当前版本。" -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:530 msgid "Update completed. Restart iac-code to continue." msgstr "更新已完成。请重启 iac-code 以继续。" -#: src/iac_code/ui/repl.py:536 +#: src/iac_code/ui/repl.py:568 msgid "No image in clipboard." msgstr "剪贴板中没有图像。" -#: src/iac_code/ui/repl.py:722 +#: src/iac_code/ui/repl.py:754 msgid "Usage: !" msgstr "用法:!" -#: src/iac_code/ui/repl.py:727 +#: src/iac_code/ui/repl.py:759 msgid "Shell command support is unavailable." msgstr "Shell 命令支持不可用。" -#: src/iac_code/ui/repl.py:791 +#: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "未知技能:${name}。输入 / 可列出命令和技能。" -#: src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "未知命令:/{name}。输入 /help 查看可用命令。" -#: src/iac_code/ui/repl.py:798 +#: src/iac_code/ui/repl.py:833 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ 只能调用技能。请改用 /{name}。" -#: src/iac_code/ui/repl.py:820 src/iac_code/ui/repl.py:865 +#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 #, python-brace-format msgid "Command error: {error}" msgstr "命令错误:{error}" -#: src/iac_code/ui/repl.py:827 +#: src/iac_code/ui/repl.py:862 #, python-brace-format msgid "Command has no handler: {name}" msgstr "命令没有处理器:{name}" -#: src/iac_code/ui/repl.py:1132 +#: src/iac_code/ui/repl.py:1167 #, python-brace-format msgid "Session not found: {session_id}" msgstr "会话不存在:{session_id}" -#: src/iac_code/ui/repl.py:1151 +#: src/iac_code/ui/repl.py:1186 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2077,31 +2103,31 @@ msgstr "" "请运行以下命令恢复:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1252 +#: src/iac_code/ui/repl.py:1287 msgid "This conversation is from a different directory." msgstr "该会话来自另一个目录。" -#: src/iac_code/ui/repl.py:1254 +#: src/iac_code/ui/repl.py:1289 msgid "To resume, run:" msgstr "请运行以下命令恢复:" -#: src/iac_code/ui/repl.py:1259 +#: src/iac_code/ui/repl.py:1294 msgid "(Command copied to clipboard)" msgstr "(命令已复制到剪贴板)" -#: src/iac_code/ui/repl.py:1416 +#: src/iac_code/ui/repl.py:1451 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " "to a vision-capable model." msgstr "当前模型 {model} 不支持图像输入。请使用 /model 切换到支持视觉的模型。" -#: src/iac_code/ui/repl.py:1425 +#: src/iac_code/ui/repl.py:1460 #, python-brace-format msgid "Image error: {err}" msgstr "图像错误:{err}" -#: src/iac_code/ui/repl.py:1442 +#: src/iac_code/ui/repl.py:1477 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." @@ -2280,6 +2306,89 @@ msgstr "{n} 小时前" msgid "{n} day{s} ago" msgstr "{n} 天前" +#: src/iac_code/ui/dialogs/skills_picker.py:52 +msgid "Search skills..." +msgstr "搜索技能..." + +#: src/iac_code/ui/dialogs/skills_picker.py:159 +msgid "Skills" +msgstr "技能" + +#: src/iac_code/ui/dialogs/skills_picker.py:161 +#, python-brace-format +msgid "{current} of {total}" +msgstr "{current} / {total}" + +#: src/iac_code/ui/dialogs/skills_picker.py:165 +#, python-brace-format +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "{count} 个技能 - 空格切换,Enter 保存,Tab 排序,Esc 取消" + +#: src/iac_code/ui/dialogs/skills_picker.py:171 +#, python-brace-format +msgid "Sort: {mode}" +msgstr "排序:{mode}" + +#: src/iac_code/ui/dialogs/skills_picker.py:176 +msgid "No skills found" +msgstr "未找到技能" + +#: src/iac_code/ui/dialogs/skills_picker.py:245 +msgid "Bundled skills cannot be disabled." +msgstr "内置技能不能被禁用。" + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "on" +msgstr "启用" + +#: src/iac_code/ui/dialogs/skills_picker.py:259 +msgid "off" +msgstr "禁用" + +#: src/iac_code/ui/dialogs/skills_picker.py:265 +msgid "locked" +msgstr "已锁定" + +#: src/iac_code/ui/dialogs/skills_picker.py:268 +msgid "matched description" +msgstr "匹配描述" + +#: src/iac_code/ui/dialogs/skills_picker.py:277 +msgid "source" +msgstr "来源" + +#: src/iac_code/ui/dialogs/skills_picker.py:279 +msgid "size" +msgstr "大小" + +#: src/iac_code/ui/dialogs/skills_picker.py:280 +msgid "name" +msgstr "名称" + +#: src/iac_code/ui/dialogs/skills_picker.py:285 +msgid "bundled" +msgstr "内置" + +#: src/iac_code/ui/dialogs/skills_picker.py:287 +msgid "project" +msgstr "项目" + +#: src/iac_code/ui/dialogs/skills_picker.py:289 +msgid "user" +msgstr "用户" + +#: src/iac_code/ui/dialogs/skills_picker.py:296 +#, python-brace-format +msgid "~{count}k tokens" +msgstr "约 {count}k 个 token" + +#: src/iac_code/ui/dialogs/skills_picker.py:297 +#, python-brace-format +msgid "~{count} tokens" +msgstr "约 {count} 个 token" + #: src/iac_code/ui/suggestions/command_provider.py:79 msgid "Search saved memories" msgstr "搜索已保存的记忆" @@ -2362,3 +2471,9 @@ msgstr " 方式 2 - 如果无法访问 github.com,运行以下命令通过 np #~ msgid "Cache create" #~ msgstr "缓存创建" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "{count} 个技能 - 空格切换,Enter 保存,/ 搜索,t 排序,Esc 取消" + diff --git a/src/iac_code/skills/management.py b/src/iac_code/skills/management.py new file mode 100644 index 0000000..a1aa72a --- /dev/null +++ b/src/iac_code/skills/management.py @@ -0,0 +1,81 @@ +"""Build enabled/disabled skill state for REPL and management UI.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from iac_code.commands.registry import PromptCommand +from iac_code.skills.discovery import skill_to_command +from iac_code.skills.settings import normalize_skill_name +from iac_code.skills.skill_definition import SkillDefinition +from iac_code.types.skill_source import SkillSource + + +@dataclass(frozen=True) +class SkillManagementItem: + """User-facing skill state for the `/skills` picker.""" + + name: str + description: str + source: SkillSource + content_length: int + path: str + enabled: bool + locked: bool + + +@dataclass(frozen=True) +class SkillManagementState: + """Enabled commands plus disabled metadata for runtime lookups.""" + + items: list[SkillManagementItem] + enabled_commands: list[PromptCommand] + disabled_commands: dict[str, PromptCommand] + locked_skill_names: set[str] + + +def build_skill_management_state( + skills: list[SkillDefinition], + disabled_skill_names: set[str], +) -> SkillManagementState: + """Apply disabled settings to discovered skills. + + Bundled skills are locked on and ignore disabled settings. + """ + disabled = {normalize_skill_name(name) for name in disabled_skill_names} + items: list[SkillManagementItem] = [] + enabled_commands: list[PromptCommand] = [] + disabled_commands: dict[str, PromptCommand] = {} + locked_skill_names: set[str] = set() + + for skill in sorted(skills, key=lambda item: item.name): + name = normalize_skill_name(skill.name) + locked = skill.source == SkillSource.BUNDLED + enabled = locked or name not in disabled + command = skill_to_command(skill) + + if locked: + locked_skill_names.add(name) + if enabled: + enabled_commands.append(command) + else: + disabled_commands[name] = command + + items.append( + SkillManagementItem( + name=skill.name, + description=skill.description, + source=skill.source, + content_length=skill.content_length, + path=skill.skill_root, + enabled=enabled, + locked=locked, + ) + ) + + return SkillManagementState( + items=items, + enabled_commands=enabled_commands, + disabled_commands=disabled_commands, + locked_skill_names=locked_skill_names, + ) diff --git a/src/iac_code/skills/settings.py b/src/iac_code/skills/settings.py new file mode 100644 index 0000000..49ca928 --- /dev/null +++ b/src/iac_code/skills/settings.py @@ -0,0 +1,61 @@ +"""Persistence helpers for skill enable/disable settings.""" + +from __future__ import annotations + +from typing import Any + +import yaml + +from iac_code.config import get_settings_path +from iac_code.utils.file_security import ensure_private_dir, ensure_private_file + + +def normalize_skill_name(name: str) -> str: + """Normalize skill names for settings and command lookup.""" + return name.lstrip("/$").strip().lower() + + +def _load_settings() -> dict[str, Any]: + path = get_settings_path() + if not path.exists(): + return {} + try: + data = yaml.safe_load(path.read_text(encoding="utf-8")) + except Exception: + return {} + return data if isinstance(data, dict) else {} + + +def load_disabled_skills() -> set[str]: + """Return normalized disabled skill names from settings.yml.""" + raw = _load_settings().get("disabled_skills") + if not isinstance(raw, list): + return set() + + disabled: set[str] = set() + for item in raw: + if not isinstance(item, str): + continue + name = normalize_skill_name(item) + if name: + disabled.add(name) + return disabled + + +def save_disabled_skills(disabled: set[str], *, locked_skill_names: set[str] | None = None) -> None: + """Persist normalized disabled skill names, preserving unrelated settings.""" + locked = {normalize_skill_name(name) for name in (locked_skill_names or set())} + normalized = sorted( + name for name in {normalize_skill_name(item) for item in disabled} if name and name not in locked + ) + + path = get_settings_path() + data = _load_settings() + if normalized: + data["disabled_skills"] = normalized + else: + data.pop("disabled_skills", None) + + ensure_private_dir(path.parent) + path.write_text(yaml.safe_dump(data, default_flow_style=False, allow_unicode=True), encoding="utf-8") + ensure_private_file(path) diff --git a/src/iac_code/skills/skill_tool.py b/src/iac_code/skills/skill_tool.py index 12a5284..2e1042e 100644 --- a/src/iac_code/skills/skill_tool.py +++ b/src/iac_code/skills/skill_tool.py @@ -32,6 +32,7 @@ def __init__( provider_manager: Any = None, tool_registry: Any = None, system_prompt: str = "", + disabled_skills: dict[str, Any] | None = None, ) -> None: self._command_registry = command_registry self._session_id = session_id @@ -39,6 +40,9 @@ def __init__( self._provider_manager = provider_manager self._tool_registry = tool_registry self._system_prompt = system_prompt + self._disabled_skills = { + self._normalize_name(name): command for name, command in (disabled_skills or {}).items() + } @property def name(self) -> str: @@ -77,6 +81,9 @@ async def execute(self, *, tool_input: dict[str, Any], context: ToolContext) -> from iac_code.commands.registry import PromptCommand + if skill_name in self._disabled_skills: + return ToolResult.error(_("Skill '{name}' is disabled. Run /skills to enable it.").format(name=skill_name)) + command = self._command_registry.get(skill_name) if not isinstance(command, PromptCommand): return ToolResult.error(f"Skill not found: '{skill_name}'") @@ -232,6 +239,12 @@ async def check_permissions(self, input: dict, context: dict | None = None) -> A from iac_code.types.permissions import PermissionResult skill_name = self._normalize_name(input.get("skill", "")) + if skill_name in self._disabled_skills: + return PermissionResult( + behavior="deny", + message=_("Skill disabled: {name}").format(name=skill_name), + ) + command = self._command_registry.get(skill_name) if not isinstance(command, PromptCommand): return PermissionResult(behavior="deny", message=f"Skill not found: {skill_name}") diff --git a/src/iac_code/ui/dialogs/skills_picker.py b/src/iac_code/ui/dialogs/skills_picker.py new file mode 100644 index 0000000..70f0551 --- /dev/null +++ b/src/iac_code/ui/dialogs/skills_picker.py @@ -0,0 +1,297 @@ +"""Interactive picker for managing skills.""" + +from __future__ import annotations + +from math import ceil +from typing import Literal + +from rich.cells import cell_len +from rich.console import Console, Group, RenderableType +from rich.text import Text + +from iac_code.i18n import _ +from iac_code.skills.management import SkillManagementItem +from iac_code.skills.settings import normalize_skill_name +from iac_code.types.skill_source import SkillSource +from iac_code.ui.components.fuzzy_picker import fuzzy_match +from iac_code.ui.components.search_box import SearchBox +from iac_code.ui.core.key_event import KeyEvent + +SortMode = Literal["name", "source", "size"] +_SORT_MODES: tuple[SortMode, ...] = ("name", "source", "size") +_SOURCE_ORDER = { + SkillSource.BUNDLED: 0, + SkillSource.PROJECT: 1, + SkillSource.USER: 2, +} + + +class SkillsPicker: + """Interactive skill enable/disable picker.""" + + def __init__( + self, + items: list[SkillManagementItem], + keybinding_manager: object | None = None, + visible_count: int = 10, + ) -> None: + self._all_items = list(items) + self._km = keybinding_manager + self._visible_count = visible_count + self._sort_mode: SortMode = "name" + self._disabled: set[str] = { + normalize_skill_name(item.name) for item in items if not item.enabled and not item.locked + } + self._filtered: list[SkillManagementItem] = [] + self._focused_index = 0 + self._visible_from = 0 + self._done = False + self._result: set[str] | None = None + self._status_message = "" + self._description_matched_names: set[str] = set() + self._search_box = SearchBox(placeholder=_("Search skills..."), on_change=self._on_query_change) + self._apply_filter() + + @property + def disabled_skill_names(self) -> set[str]: + return set(self._disabled) + + @property + def filtered_items(self) -> list[SkillManagementItem]: + return list(self._filtered) + + @property + def result(self) -> set[str] | None: + return None if self._result is None else set(self._result) + + @property + def done(self) -> bool: + return self._done + + @property + def status_message(self) -> str: + return self._status_message + + @property + def sort_mode(self) -> SortMode: + return self._sort_mode + + def run(self) -> set[str] | None: + """Run the blocking terminal picker.""" + from iac_code.ui.core.in_place_render import InPlaceRenderer + from iac_code.ui.core.raw_input import RawInputCapture + + console = Console() + renderer = InPlaceRenderer(console) + self._done = False + self._result = None + + def cursor_pos() -> tuple[int, int]: + sb = self._search_box + col = 2 if not sb.value else 2 + cell_len(sb.value[: sb.cursor]) + return (3, col) + + try: + with RawInputCapture() as cap: + while not self._done: + renderer.render(self.render(), cursor_to=cursor_pos()) + key_event = cap.read_key(timeout=0.1) + if key_event is not None: + self.handle_key(key_event) + except OSError: + return None + finally: + renderer.clear() + + return self.result + + def handle_key(self, key_event: KeyEvent) -> bool: + key = key_event.key + ctrl = key_event.ctrl + + if ctrl and key == "c": + self._done = True + self._result = None + return True + + if key == "escape": + self._done = True + self._result = None + return True + + if key == "enter": + self._done = True + self._result = set(self._disabled) + return True + + if key == "up" or (ctrl and key == "p"): + self._move_focus(-1) + return True + if key == "down" or (ctrl and key == "n"): + self._move_focus(1) + return True + if key == "pageup": + self._move_focus(-self._visible_count) + return True + if key == "pagedown": + self._move_focus(self._visible_count) + return True + + if key == " ": + self._toggle_focused() + return True + + if key == "tab": + self._cycle_sort() + return True + + consumed = self._search_box.handle_key(key_event) + if consumed: + self._status_message = "" + return consumed + + def render(self) -> RenderableType: + parts: list[RenderableType] = [] + total = len(self._filtered) + focus_pos = (self._focused_index + 1) if total else 0 + + header = Text() + header.append(_("Skills"), style="bold cyan") + if total: + header.append(" (" + _("{current} of {total}").format(current=focus_pos, total=total) + ")", style="dim") + parts.append(header) + parts.append( + Text( + _("{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel").format( + count=len(self._all_items) + ), + style="dim", + ) + ) + parts.append(Text(_("Sort: {mode}").format(mode=_sort_mode_label(self._sort_mode)), style="dim")) + parts.append(self._search_box.render()) + parts.append(Text("")) + + if not self._filtered: + parts.append(Text(_("No skills found"), style="dim")) + else: + for item in self._filtered[self._visible_from : self._visible_from + self._visible_count]: + parts.append(self._render_item(item, item == self._filtered[self._focused_index])) + + parts.append(Text("")) + if self._status_message: + parts.append(Text(self._status_message, style="yellow")) + return Group(*parts) + + def _on_query_change(self, _query: str) -> None: + self._apply_filter() + + def _apply_filter(self, keep_focus_name: str | None = None) -> None: + query = self._search_box.value.strip() + candidates = list(self._all_items) + self._description_matched_names = set() + if query: + scored: list[tuple[float, SkillManagementItem]] = [] + for item in candidates: + haystack = f"{item.name} {item.description}" + score = fuzzy_match(query, haystack) + if score is not None: + scored.append((score, item)) + if fuzzy_match(query, item.name) is None: + self._description_matched_names.add(item.name) + scored.sort(key=lambda pair: pair[0], reverse=True) + candidates = [item for _, item in scored] + + self._filtered = self._sort_items(candidates) + if keep_focus_name is not None: + for index, item in enumerate(self._filtered): + if item.name == keep_focus_name: + self._focused_index = index + break + else: + self._focused_index = 0 + else: + self._focused_index = 0 + self._visible_from = 0 + + def _sort_items(self, items: list[SkillManagementItem]) -> list[SkillManagementItem]: + if self._sort_mode == "source": + return sorted(items, key=lambda item: (_SOURCE_ORDER.get(item.source, 99), item.name)) + if self._sort_mode == "size": + return sorted(items, key=lambda item: (item.content_length, item.name)) + return sorted(items, key=lambda item: item.name) + + def _cycle_sort(self) -> None: + current = _SORT_MODES.index(self._sort_mode) + self._sort_mode = _SORT_MODES[(current + 1) % len(_SORT_MODES)] + focused = self._filtered[self._focused_index].name if self._filtered else None + self._apply_filter(keep_focus_name=focused) + + def _move_focus(self, delta: int) -> None: + if not self._filtered: + return + self._focused_index = max(0, min(self._focused_index + delta, len(self._filtered) - 1)) + if self._focused_index < self._visible_from: + self._visible_from = self._focused_index + elif self._focused_index >= self._visible_from + self._visible_count: + self._visible_from = self._focused_index - self._visible_count + 1 + + def _toggle_focused(self) -> None: + if not self._filtered: + return + item = self._filtered[self._focused_index] + name = normalize_skill_name(item.name) + if item.locked: + self._status_message = _("Bundled skills cannot be disabled.") + return + if name in self._disabled: + self._disabled.remove(name) + else: + self._disabled.add(name) + self._status_message = "" + self._apply_filter(keep_focus_name=item.name) + + def _render_item(self, item: SkillManagementItem, is_focused: bool) -> Text: + text = Text() + text.append("> " if is_focused else " ", style="bold cyan" if is_focused else "") + enabled = item.locked or normalize_skill_name(item.name) not in self._disabled + state_marker = "- " if enabled else "x " + state_label = _("on") if enabled else _("off") + text.append("{}{} ".format(state_marker, state_label), style="green" if enabled else "red") + text.append(f" {item.name:<18}", style="bold" if is_focused else "") + + details = [_source_label(item.source)] + if item.locked: + details.append(_("locked")) + details.append(_format_token_estimate(item.content_length)) + if item.name in self._description_matched_names: + details.append(_("matched description")) + if item.source != SkillSource.BUNDLED and item.path: + details.append(item.path) + text.append(" - " + " - ".join(details), style="dim") + return text + + +def _sort_mode_label(mode: SortMode) -> str: + if mode == "source": + return _("source") + if mode == "size": + return _("size") + return _("name") + + +def _source_label(source: SkillSource) -> str: + if source == SkillSource.BUNDLED: + return _("bundled") + if source == SkillSource.PROJECT: + return _("project") + if source == SkillSource.USER: + return _("user") + return source.value + + +def _format_token_estimate(content_length: int) -> str: + tokens = max(1, ceil(content_length / 4)) + if tokens >= 1000: + return _("~{count}k tokens").format(count=f"{tokens / 1000:.1f}") + return _("~{count} tokens").format(count=tokens) diff --git a/src/iac_code/ui/repl.py b/src/iac_code/ui/repl.py index 003e2ea..22c54ea 100644 --- a/src/iac_code/ui/repl.py +++ b/src/iac_code/ui/repl.py @@ -44,6 +44,7 @@ start_background_update_check, suppress_version, ) +from iac_code.skills.settings import normalize_skill_name from iac_code.state import AppStateStore from iac_code.state.app_state import AppState from iac_code.tasks.notification_queue import NotificationQueue @@ -167,45 +168,10 @@ def __init__( self.tool_registry.register(TaskGetTool(self._task_manager)) self.tool_registry.register(TaskStopTool(self._task_manager)) - # === Skill system initialization === - from iac_code.skills.bundled import init_bundled_skills - from iac_code.skills.discovery import discover_all_skills, skill_to_command - from iac_code.skills.listing import build_skill_listing - from iac_code.skills.skill_tool import SkillTool - - # 1. Initialize bundled skills (once) - init_bundled_skills() - - # 2. Discover all skills and register to unified CommandRegistry cwd = os.getcwd() - all_skills = discover_all_skills(cwd) - for skill in all_skills: - cmd = skill_to_command(skill) - existing = self.command_registry.get(cmd.name) - if existing is not None and not isinstance(existing, PromptCommand): - logger.warning( - "Skill '%s' (source=%s) skipped: conflicts with built-in command", - cmd.name, - cmd.source, - ) - continue - self.command_registry.register(cmd) - - # 3. Register SkillTool - self.tool_registry.register( - SkillTool( - command_registry=self.command_registry, - session_id=self._session_id, - cwd=cwd, - provider_manager=self._provider_manager, - tool_registry=self.tool_registry, - system_prompt=build_system_prompt(cwd=cwd, memory_content=memory_content), - ) - ) - - # 4. Generate skill listing for system prompt + self._memory_content = memory_content + self.refresh_skills() skill_commands = self.command_registry.get_model_invocable_skills() - self._skill_listing = build_skill_listing(skill_commands) from iac_code.services.permissions.loader import load_permission_context @@ -279,6 +245,72 @@ def __init__( # Public entry-point # ------------------------------------------------------------------ + @property + def skill_management_items(self): + """Return all discovered skills with management state.""" + return getattr(self, "_skill_management_items", []) + + @property + def locked_skill_names(self): + """Return skill names that cannot be disabled.""" + return getattr(self, "_locked_skill_names", set()) + + def refresh_skills(self) -> None: + """Rediscover skills and refresh enabled/disabled skill state.""" + from iac_code.skills.bundled import init_bundled_skills + from iac_code.skills.discovery import discover_all_skills + from iac_code.skills.listing import build_skill_listing + from iac_code.skills.management import build_skill_management_state + from iac_code.skills.settings import load_disabled_skills + from iac_code.skills.skill_tool import SkillTool + + init_bundled_skills() + cwd = os.getcwd() + all_skills = discover_all_skills(cwd) + state = build_skill_management_state(all_skills, load_disabled_skills()) + self._skill_management_items = state.items + self._disabled_skill_commands = state.disabled_commands + self._locked_skill_names = state.locked_skill_names + + self.command_registry.clear_prompt_commands() + for cmd in state.enabled_commands: + existing = self.command_registry.get(cmd.name) + if existing is not None and not isinstance(existing, PromptCommand): + logger.warning( + "Skill '%s' (source=%s) skipped: conflicts with built-in command", + cmd.name, + cmd.source, + ) + continue + self.command_registry.register(cmd) + + memory_content = getattr(self, "_memory_content", "") + self.tool_registry.register( + SkillTool( + command_registry=self.command_registry, + disabled_skills=self._disabled_skill_commands, + session_id=self._session_id, + cwd=cwd, + provider_manager=self._provider_manager, + tool_registry=self.tool_registry, + system_prompt=build_system_prompt(cwd=cwd, memory_content=memory_content), + ) + ) + + skill_commands = self.command_registry.get_model_invocable_skills() + self._skill_listing = build_skill_listing(skill_commands) + + if hasattr(self, "_agent_loop"): + self._agent_loop.set_auto_trigger_skills(skill_commands) + self._agent_loop.set_provider( + self._provider_manager, + system_prompt=build_system_prompt( + cwd=cwd, + memory_content=memory_content, + skill_listing=self._skill_listing, + ), + ) + async def run(self, initial_prompt: str | None = None) -> None: """Run the REPL until the user exits. @@ -787,6 +819,9 @@ def _emit_error(message: str) -> None: self.renderer.print_system_message(message, style="red") if cmd is None: + if is_skill_trigger and normalize_skill_name(name) in getattr(self, "_disabled_skill_commands", {}): + _emit_error(_("Skill '{name}' is disabled. Run /skills to enable it.").format(name=name)) + return if is_skill_trigger: _emit_error(_("Unknown skill: ${name}. Type / to list commands and skills.").format(name=name)) else: diff --git a/tests/agent/test_agent_loop_new.py b/tests/agent/test_agent_loop_new.py index baf9852..4c6bc8b 100644 --- a/tests/agent/test_agent_loop_new.py +++ b/tests/agent/test_agent_loop_new.py @@ -60,6 +60,22 @@ def test_get_tool_definitions(self, mock_provider): assert defs[0].name == "read_file" assert defs[0].description == "Read file" + def test_set_auto_trigger_skills_refreshes_candidates(self, mock_provider, mock_registry): + old_command = SimpleNamespace(name="old-skill") + new_command = SimpleNamespace(name="new-skill") + loop = AgentLoop( + provider_manager=mock_provider, + system_prompt="test", + tool_registry=mock_registry, + auto_trigger_skills=[old_command], + ) + loop._auto_loaded_skills.add("old-skill") + + loop.set_auto_trigger_skills([new_command]) + + assert loop._auto_trigger_skills == [new_command] + assert loop._auto_loaded_skills == {"old-skill"} + def test_get_provider_messages_converts_strings_and_blocks(self, mock_provider, mock_registry): loop = AgentLoop(provider_manager=mock_provider, system_prompt="test", tool_registry=mock_registry) loop.context_manager = MagicMock() diff --git a/tests/cli/test_headless.py b/tests/cli/test_headless.py index 286634d..e026487 100644 --- a/tests/cli/test_headless.py +++ b/tests/cli/test_headless.py @@ -15,6 +15,8 @@ from iac_code.cli.headless import EXIT_ERROR, EXIT_MAX_TURNS, EXIT_OK, HeadlessRunner from iac_code.cli.output_formats import OutputFormat from iac_code.providers.manager import ProviderNotConfiguredError +from iac_code.skills.frontmatter import SkillFrontmatter +from iac_code.skills.skill_definition import SkillDefinition from iac_code.types.stream_events import ( ErrorEvent, MessageEndEvent, @@ -757,6 +759,10 @@ def __init__(self, name="prompt", **kwargs): "iac_code.skills.discovery.skill_to_command", lambda skill: SimpleNamespace(name=skill.name), ) + monkeypatch.setattr( + "iac_code.skills.management.skill_to_command", + lambda skill: SimpleNamespace(name=skill.name), + ) monkeypatch.setattr("iac_code.skills.listing.build_skill_listing", lambda skill_commands: "skill listing") monkeypatch.setattr( "iac_code.agent.system_prompt.build_system_prompt", @@ -806,7 +812,12 @@ def test_create_agent_loop_builds_expected_dependencies(monkeypatch): def test_create_agent_loop_handles_credential_load_failure_and_skill_conflict(monkeypatch): runner = _make_runner() existing_cmd = {"skill-one": object()} - skill = SimpleNamespace(name="skill-one") + skill = SkillDefinition( + name="skill-one", + description="skill-one description", + frontmatter=SkillFrontmatter(description="skill-one description"), + content="", + ) captured, fake_registry, fake_command_registry = _install_headless_fakes( monkeypatch, creds=None, diff --git a/tests/commands/test_registry.py b/tests/commands/test_registry.py index d7b6a51..053a692 100644 --- a/tests/commands/test_registry.py +++ b/tests/commands/test_registry.py @@ -1,7 +1,7 @@ """Tests for the commands/registry module.""" from iac_code.commands import create_default_registry -from iac_code.commands.registry import CommandRegistry, LocalCommand, _subsequence_score +from iac_code.commands.registry import CommandRegistry, LocalCommand, PromptCommand, _subsequence_score async def dummy_handler(**kwargs): @@ -190,6 +190,21 @@ def test_get_completions_sorted(self): completions = registry.get_completions("m") assert completions == ["map", "mark", "model"] + def test_clear_prompt_commands_preserves_local_commands(self): + """Test clearing prompt commands removes skills while preserving built-ins.""" + registry = CommandRegistry() + local = LocalCommand(name="help", description="Help", handler=dummy_handler, aliases=["?"]) + skill = PromptCommand(name="deploy", description="Deploy", aliases=["d"]) + registry.register(local) + registry.register(skill) + + registry.clear_prompt_commands() + + assert registry.get("help") is local + assert registry.get("?") is local + assert registry.get("deploy") is None + assert registry.get("d") is None + class TestCreateDefaultRegistry: """Tests for create_default_registry function.""" @@ -199,11 +214,11 @@ def test_create_default_registry_returns_registry(self): registry = create_default_registry() assert isinstance(registry, CommandRegistry) - def test_create_default_registry_has_11_commands(self): - """Test create_default_registry has 11 commands.""" + def test_create_default_registry_has_12_commands(self): + """Test create_default_registry has 12 commands.""" registry = create_default_registry() all_cmds = registry.get_all() - assert len(all_cmds) == 11 + assert len(all_cmds) == 12 def test_create_default_registry_command_names(self): """Test create_default_registry has expected command names.""" @@ -221,6 +236,7 @@ def test_create_default_registry_command_names(self): "effort", "resume", "memory", + "skills", "status", } diff --git a/tests/commands/test_skills.py b/tests/commands/test_skills.py new file mode 100644 index 0000000..6804165 --- /dev/null +++ b/tests/commands/test_skills.py @@ -0,0 +1,52 @@ +"""Tests for the /skills command.""" + +from __future__ import annotations + +from unittest.mock import MagicMock + +import pytest + +from iac_code.commands.skills import skills_command + + +@pytest.mark.asyncio +async def test_skills_no_context_returns_message(): + result = await skills_command(context=None, args=[]) + + assert "interactive" in result.lower() + + +@pytest.mark.asyncio +async def test_skills_cancel_does_not_save(monkeypatch): + repl = MagicMock() + repl.skill_management_items = [] + context = MagicMock(repl=repl) + fake_picker = MagicMock() + fake_picker.run.return_value = None + monkeypatch.setattr("iac_code.ui.dialogs.skills_picker.SkillsPicker", MagicMock(return_value=fake_picker)) + save_mock = MagicMock() + monkeypatch.setattr("iac_code.commands.skills.save_disabled_skills", save_mock) + + result = await skills_command(context=context, args=[]) + + assert "cancel" in result.lower() + save_mock.assert_not_called() + + +@pytest.mark.asyncio +async def test_skills_save_persists_and_refreshes(monkeypatch): + repl = MagicMock() + repl.skill_management_items = [] + repl.locked_skill_names = {"iac-aliyun"} + context = MagicMock(repl=repl) + fake_picker = MagicMock() + fake_picker.run.return_value = {"team-review"} + monkeypatch.setattr("iac_code.ui.dialogs.skills_picker.SkillsPicker", MagicMock(return_value=fake_picker)) + save_mock = MagicMock() + monkeypatch.setattr("iac_code.commands.skills.save_disabled_skills", save_mock) + + result = await skills_command(context=context, args=[]) + + assert "updated" in result.lower() + save_mock.assert_called_once_with({"team-review"}, locked_skill_names={"iac-aliyun"}) + repl.refresh_skills.assert_called_once() diff --git a/tests/skills/test_management.py b/tests/skills/test_management.py new file mode 100644 index 0000000..268e90d --- /dev/null +++ b/tests/skills/test_management.py @@ -0,0 +1,40 @@ +"""Tests for skill management state construction.""" + +from __future__ import annotations + +from iac_code.skills.frontmatter import SkillFrontmatter +from iac_code.skills.management import build_skill_management_state +from iac_code.skills.skill_definition import SkillDefinition +from iac_code.types.skill_source import SkillSource + + +def _skill(name: str, source: SkillSource, *, content: str = "abcd") -> SkillDefinition: + return SkillDefinition( + name=name, + description=f"{name} description", + frontmatter=SkillFrontmatter(description=f"{name} description"), + content=content, + content_length=len(content), + source=source, + skill_root=f"/repo/{name}", + ) + + +def test_disabled_non_bundled_skills_are_split_out(): + state = build_skill_management_state( + [_skill("team-review", SkillSource.PROJECT), _skill("iac-aliyun", SkillSource.BUNDLED)], + {"team-review"}, + ) + + assert [cmd.name for cmd in state.enabled_commands] == ["iac-aliyun"] + assert state.disabled_commands["team-review"].name == "team-review" + assert {item.name: item.enabled for item in state.items} == {"team-review": False, "iac-aliyun": True} + + +def test_bundled_skills_ignore_disabled_setting_and_are_locked(): + state = build_skill_management_state([_skill("iac-aliyun", SkillSource.BUNDLED)], {"iac-aliyun"}) + + assert [cmd.name for cmd in state.enabled_commands] == ["iac-aliyun"] + assert state.disabled_commands == {} + assert state.items[0].enabled is True + assert state.items[0].locked is True diff --git a/tests/skills/test_settings.py b/tests/skills/test_settings.py new file mode 100644 index 0000000..f776709 --- /dev/null +++ b/tests/skills/test_settings.py @@ -0,0 +1,50 @@ +"""Tests for skill settings persistence.""" + +from __future__ import annotations + +import yaml + +from iac_code.skills.settings import load_disabled_skills, save_disabled_skills + + +def test_load_disabled_skills_missing_file(monkeypatch, tmp_path): + settings = tmp_path / "settings.yml" + monkeypatch.setattr("iac_code.skills.settings.get_settings_path", lambda: settings) + + assert load_disabled_skills() == set() + + +def test_load_disabled_skills_ignores_invalid_yaml(monkeypatch, tmp_path): + settings = tmp_path / "settings.yml" + settings.write_text("[", encoding="utf-8") + monkeypatch.setattr("iac_code.skills.settings.get_settings_path", lambda: settings) + + assert load_disabled_skills() == set() + + +def test_load_disabled_skills_ignores_non_list(monkeypatch, tmp_path): + settings = tmp_path / "settings.yml" + settings.write_text(yaml.safe_dump({"disabled_skills": "demo"}), encoding="utf-8") + monkeypatch.setattr("iac_code.skills.settings.get_settings_path", lambda: settings) + + assert load_disabled_skills() == set() + + +def test_load_disabled_skills_normalizes_strings(monkeypatch, tmp_path): + settings = tmp_path / "settings.yml" + settings.write_text(yaml.safe_dump({"disabled_skills": [" Demo ", "", 7, "Other"]}), encoding="utf-8") + monkeypatch.setattr("iac_code.skills.settings.get_settings_path", lambda: settings) + + assert load_disabled_skills() == {"demo", "other"} + + +def test_save_disabled_skills_preserves_other_settings_and_excludes_locked(monkeypatch, tmp_path): + settings = tmp_path / "settings.yml" + settings.write_text(yaml.safe_dump({"activeProvider": "dashscope"}), encoding="utf-8") + monkeypatch.setattr("iac_code.skills.settings.get_settings_path", lambda: settings) + + save_disabled_skills({"beta", "alpha", "iac-aliyun"}, locked_skill_names={"iac-aliyun"}) + + data = yaml.safe_load(settings.read_text(encoding="utf-8")) + assert data["activeProvider"] == "dashscope" + assert data["disabled_skills"] == ["alpha", "beta"] diff --git a/tests/skills/test_skill_tool.py b/tests/skills/test_skill_tool.py index 116653b..4722a57 100644 --- a/tests/skills/test_skill_tool.py +++ b/tests/skills/test_skill_tool.py @@ -77,6 +77,17 @@ async def test_execute_not_found(self): assert result.is_error assert "not found" in result.content.lower() + @pytest.mark.asyncio + async def test_execute_disabled_skill_returns_disabled_error(self): + registry = CommandRegistry() + tool = SkillTool(command_registry=registry, disabled_skills={"demo": object()}) + + result = await tool.execute(tool_input={"skill": "demo"}, context=ToolContext()) + + assert result.is_error + assert "disabled" in result.content.lower() + assert "/skills" in result.content + @pytest.mark.asyncio async def test_execute_with_args(self): registry = _make_registry_with_skill(content="Process $ARGUMENTS") @@ -237,6 +248,16 @@ async def test_nonexistent_skill_denied(self): result = await tool.check_permissions({"skill": "nonexistent"}) assert result.behavior == "deny" + @pytest.mark.asyncio + async def test_disabled_skill_denied(self): + registry = CommandRegistry() + tool = SkillTool(command_registry=registry, disabled_skills={"demo": object()}) + + result = await tool.check_permissions({"skill": "demo"}) + + assert result.behavior == "deny" + assert "disabled" in result.message.lower() + @pytest.mark.asyncio async def test_permission_message_includes_project_source(self): registry = _make_registry_with_skill(source=SkillSource.PROJECT, allowed_tools=["bash(*)"]) diff --git a/tests/ui/dialogs/test_skills_picker.py b/tests/ui/dialogs/test_skills_picker.py new file mode 100644 index 0000000..d8d095e --- /dev/null +++ b/tests/ui/dialogs/test_skills_picker.py @@ -0,0 +1,159 @@ +"""Tests for SkillsPicker dialog.""" + +from __future__ import annotations + +from rich.console import Console + +from iac_code.skills.management import SkillManagementItem +from iac_code.types.skill_source import SkillSource +from iac_code.ui.core.key_event import KeyEvent +from iac_code.ui.dialogs.skills_picker import SkillsPicker + + +def key(name: str, char: str | None = None, *, ctrl: bool = False) -> KeyEvent: + return KeyEvent(key=name, char=name if char is None else char, ctrl=ctrl) + + +def _item( + name: str, + source: SkillSource, + *, + enabled: bool = True, + locked: bool = False, + size: int = 100, + description: str | None = None, +) -> SkillManagementItem: + return SkillManagementItem( + name=name, + description=description or f"{name} description", + source=source, + content_length=size, + path=f"/repo/{name}", + enabled=enabled, + locked=locked, + ) + + +def _render_text(picker: SkillsPicker) -> str: + console = Console(record=True, width=140) + console.print(picker.render()) + return console.export_text() + + +def test_space_toggles_non_bundled_skill(): + picker = SkillsPicker([_item("team-review", SkillSource.PROJECT)]) + + picker.handle_key(key(" ", " ")) + + assert picker.disabled_skill_names == {"team-review"} + + +def test_space_does_not_toggle_locked_bundled_skill(): + picker = SkillsPicker([_item("iac-aliyun", SkillSource.BUNDLED, locked=True)]) + + picker.handle_key(key(" ", " ")) + + assert picker.disabled_skill_names == set() + assert "cannot be disabled" in picker.status_message.lower() + + +def test_enter_returns_disabled_set(): + picker = SkillsPicker([_item("team-review", SkillSource.PROJECT)]) + picker.handle_key(key(" ", " ")) + + picker.handle_key(key("enter", "")) + + assert picker.result == {"team-review"} + assert picker.done is True + + +def test_escape_cancels(): + picker = SkillsPicker([_item("team-review", SkillSource.PROJECT)]) + + picker.handle_key(key("escape", "")) + + assert picker.result is None + assert picker.done is True + + +def test_search_filters_by_name_and_description(): + picker = SkillsPicker( + [ + _item("team-review", SkillSource.PROJECT, description="review"), + _item("deploy", SkillSource.USER, description="deploy"), + ] + ) + + picker.handle_key(key("r", "r")) + picker.handle_key(key("e", "e")) + picker.handle_key(key("v", "v")) + + assert [item.name for item in picker.filtered_items] == ["team-review"] + + +def test_slash_is_search_text(): + picker = SkillsPicker( + [ + _item("team/review", SkillSource.PROJECT), + _item("deploy", SkillSource.USER), + ] + ) + + picker.handle_key(key("/", "/")) + + assert [item.name for item in picker.filtered_items] == ["team/review"] + + +def test_t_is_search_text(): + picker = SkillsPicker( + [ + _item("team-review", SkillSource.PROJECT, description="team"), + _item("deploy", SkillSource.USER, description="deploy"), + ] + ) + + picker.handle_key(key("t", "t")) + + assert [item.name for item in picker.filtered_items] == ["team-review"] + assert picker.sort_mode == "name" + + +def test_description_only_match_is_labeled(): + picker = SkillsPicker( + [ + _item("iac-aliyun", SkillSource.BUNDLED, description="Terraform template", locked=True), + ] + ) + + picker.handle_key(key("t", "t")) + + assert "matched description" in _render_text(picker) + + +def test_name_match_does_not_show_description_label(): + picker = SkillsPicker( + [ + _item("team-review", SkillSource.PROJECT, description="Terraform template"), + ] + ) + + picker.handle_key(key("t", "t")) + + assert "matched description" not in _render_text(picker) + + +def test_tab_cycles_sort_by_source_then_size(): + picker = SkillsPicker( + [ + _item("zeta", SkillSource.USER, size=400), + _item("alpha", SkillSource.PROJECT, size=800), + _item("bundled", SkillSource.BUNDLED, locked=True, size=100), + ] + ) + assert [item.name for item in picker.filtered_items] == ["alpha", "bundled", "zeta"] + + picker.handle_key(key("tab", "\t")) + assert [item.name for item in picker.filtered_items] == ["bundled", "alpha", "zeta"] + + picker.handle_key(key("tab", "\t")) + assert [item.name for item in picker.filtered_items] == ["bundled", "zeta", "alpha"] diff --git a/tests/ui/suggestions/test_skill_provider.py b/tests/ui/suggestions/test_skill_provider.py index 56e636a..674e9a5 100644 --- a/tests/ui/suggestions/test_skill_provider.py +++ b/tests/ui/suggestions/test_skill_provider.py @@ -46,6 +46,16 @@ def test_empty_query_returns_only_skills(self, provider): assert "help" not in names assert "model" not in names + def test_disabled_skills_not_suggested_when_not_registered(self): + """Disabled skills are omitted from registry, so '$' suggests enabled skills only.""" + reg = CommandRegistry() + reg.register(PromptCommand(name="enabled", description="Enabled skill")) + provider = SkillProvider(reg) + + items = provider.provide(make_token("$")) + + assert {item.display_text for item in items} == {"enabled"} + def test_partial_match_skill(self, provider): """'$dep' → results contain 'deploy'.""" items = provider.provide(make_token("$dep")) diff --git a/tests/ui/test_repl_integration.py b/tests/ui/test_repl_integration.py index 50c63d3..624d964 100644 --- a/tests/ui/test_repl_integration.py +++ b/tests/ui/test_repl_integration.py @@ -200,6 +200,79 @@ async def test_run_once_routes_normal_chat_unchanged(): repl._handle_chat.assert_awaited_once_with("hello") +@pytest.mark.asyncio +async def test_handle_command_reports_disabled_skill(): + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl.command_registry = SimpleNamespace(parse=Mock(return_value=("Disabled", [])), get=Mock(return_value=None)) + repl._disabled_skill_commands = {"disabled": object()} + repl._agent_loop = SimpleNamespace(context_manager=SimpleNamespace(get_messages=Mock(return_value=[]))) + repl._command_log = [] + repl.renderer = SimpleNamespace(print_system_message=Mock()) + + await repl._handle_command("$Disabled") + + repl.renderer.print_system_message.assert_called_once() + message = repl.renderer.print_system_message.call_args.args[0] + assert "disabled" in message.lower() + assert "/skills" in message + + +@patch("iac_code.ui.repl.ProviderManager") +@patch("iac_code.ui.repl.SessionStorage") +@patch("iac_code.ui.repl.MemoryManager") +def test_init_does_not_register_disabled_project_skill(mock_mm, mock_ss, mock_pm, monkeypatch): + from iac_code.skills.frontmatter import SkillFrontmatter + from iac_code.skills.skill_definition import SkillDefinition + from iac_code.types.skill_source import SkillSource + from iac_code.ui.repl import InlineREPL + + project_skill = SkillDefinition( + name="project-skill", + description="Project skill", + frontmatter=SkillFrontmatter(description="Project skill"), + content="Body", + source=SkillSource.PROJECT, + ) + monkeypatch.setattr("iac_code.skills.discovery.discover_all_skills", lambda cwd: [project_skill]) + monkeypatch.setattr("iac_code.skills.settings.load_disabled_skills", lambda: {"project-skill"}) + + repl = InlineREPL(model="test-model") + + assert repl.command_registry.get("project-skill") is None + assert "project-skill" in repl._disabled_skill_commands + + +@patch("iac_code.ui.repl.ProviderManager") +@patch("iac_code.ui.repl.SessionStorage") +@patch("iac_code.ui.repl.MemoryManager") +def test_refresh_skills_updates_agent_loop_auto_trigger_skills(mock_mm, mock_ss, mock_pm, monkeypatch): + from iac_code.skills.frontmatter import SkillFrontmatter + from iac_code.skills.skill_definition import SkillDefinition + from iac_code.types.skill_source import SkillSource + from iac_code.ui.repl import InlineREPL + + disabled: set[str] = set() + project_skill = SkillDefinition( + name="project-skill", + description="Project skill", + frontmatter=SkillFrontmatter(description="Project skill", auto_trigger={"script": "auto_trigger.py"}), + content="Body", + source=SkillSource.PROJECT, + ) + monkeypatch.setattr("iac_code.skills.discovery.discover_all_skills", lambda cwd: [project_skill]) + monkeypatch.setattr("iac_code.skills.settings.load_disabled_skills", lambda: disabled) + + repl = InlineREPL(model="test-model") + assert any(command.name == "project-skill" for command in repl._agent_loop._auto_trigger_skills) + + disabled.add("project-skill") + repl.refresh_skills() + + assert all(command.name != "project-skill" for command in repl._agent_loop._auto_trigger_skills) + + @patch("iac_code.ui.repl.ProviderManager") @patch("iac_code.ui.repl.SessionStorage") @patch("iac_code.ui.repl.MemoryManager") From 66bb6809e72d19880c567b22e85766fb7f904e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 11:01:34 +0800 Subject: [PATCH 04/11] feat: add aliyun oauth browser login --- src/iac_code/commands/auth.py | 246 +++++- .../i18n/locales/de/LC_MESSAGES/messages.po | 728 ++++++++--------- .../i18n/locales/es/LC_MESSAGES/messages.po | 743 ++++++++--------- .../i18n/locales/fr/LC_MESSAGES/messages.po | 744 +++++++++--------- .../i18n/locales/ja/LC_MESSAGES/messages.po | 596 +++++++------- .../i18n/locales/pt/LC_MESSAGES/messages.po | 703 +++++++++-------- .../i18n/locales/zh/LC_MESSAGES/messages.po | 548 +++++++------ src/iac_code/services/providers/aliyun.py | 112 ++- .../services/providers/aliyun_oauth.py | 583 ++++++++++++++ src/iac_code/tools/cloud/aliyun/aliyun_api.py | 11 +- src/iac_code/tools/cloud/aliyun/ros_client.py | 11 +- tests/commands/test_auth_flows.py | 279 +++++++ tests/services/providers/test_aliyun.py | 301 +++++++ tests/services/providers/test_aliyun_oauth.py | 605 ++++++++++++++ tests/test_i18n.py | 25 + tests/tools/cloud/aliyun/test_aliyun_api.py | 133 ++++ tests/tools/cloud/aliyun/test_ros_client.py | 55 ++ 17 files changed, 4445 insertions(+), 1978 deletions(-) create mode 100644 src/iac_code/services/providers/aliyun_oauth.py create mode 100644 tests/services/providers/test_aliyun_oauth.py diff --git a/src/iac_code/commands/auth.py b/src/iac_code/commands/auth.py index d8ef820..b3afff2 100644 --- a/src/iac_code/commands/auth.py +++ b/src/iac_code/commands/auth.py @@ -4,8 +4,11 @@ import os import sys +import threading import unicodedata from collections.abc import Callable +from contextlib import contextmanager +from datetime import datetime from typing import TYPE_CHECKING, TypedDict from urllib.parse import urlparse @@ -102,6 +105,11 @@ def _build_providers_from_registry() -> list[LLMProvider]: _C_BOLD = "\033[1m" _BACK = _BackSentinel() +_ALIYUN_EPOCH_FIELDS = { + "oauth_access_token_expire", + "oauth_refresh_token_expire", + "sts_expiration", +} # ── Data helpers ────────────────────────────────────────────────────── @@ -1173,29 +1181,245 @@ def _nb(timeout=0.05): def _render_credential_info(credential: AliyunCredential, source: str) -> None: """Write current credential info lines (called between title and options).""" - from iac_code.services.providers.aliyun import MODE_DISPLAY_NAMES, MODE_FIELDS, mask_sensitive + from iac_code.services.providers.aliyun import MODE_FIELDS, mask_sensitive _write(" {}{} ({}){}\n".format(_C_DIM, _("Current configuration"), source, _C_RST)) - mode_display = _(MODE_DISPLAY_NAMES.get(credential.mode, credential.mode)) + mode_display = _aliyun_credential_mode_label(credential.mode) _write(" {}{}: {}{}\n".format(_C_DIM, _("Mode"), mode_display, _C_RST)) mode_fields = MODE_FIELDS.get(credential.mode, []) for field_name, label, sensitive in mode_fields: - value = getattr(credential, field_name, "") - if value and sensitive: - value = mask_sensitive(value) + raw_value = getattr(credential, field_name, "") + value = _format_aliyun_credential_field_value(field_name, raw_value, sensitive, mask_sensitive) display_value = value if value else _("(not set)") - _write(f" {_C_DIM}{label}: {display_value}{_C_RST}\n") + _write(" {}{}: {}{}\n".format(_C_DIM, _aliyun_credential_field_label(label), display_value, _C_RST)) _write(" {}{}: {}{}\n".format(_C_DIM, _("Region"), credential.region_id, _C_RST)) _write("\n") +def _aliyun_credential_mode_label(mode: str) -> str: + if mode == "AK": + return _("AccessKey") + if mode == "StsToken": + return _("STS Token") + if mode == "RamRoleArn": + return _("RAM Role") + if mode == "OAuth": + return _("OAuth Login (Browser)") + return mode + + +def _aliyun_credential_field_label(label: str) -> str: + translations = { + "AccessKey ID": _("AccessKey ID"), + "AccessKey Secret": _("AccessKey Secret"), + "STS Token": _("STS Token"), + "RAM Role ARN": _("RAM Role ARN"), + "Session Name": _("Session Name"), + "OAuth Site Type": _("OAuth Site Type"), + "OAuth Access Token": _("OAuth Access Token"), + "OAuth Refresh Token": _("OAuth Refresh Token"), + "OAuth Access Token Expire": _("OAuth Access Token Expire"), + "OAuth Refresh Token Expire": _("OAuth Refresh Token Expire"), + "STS Expiration": _("STS Expiration"), + } + return translations.get(label, label) + + +def _format_aliyun_credential_field_value( + field_name: str, + raw_value: object, + sensitive: bool, + mask_sensitive: Callable[[str], str], +) -> str: + if field_name in _ALIYUN_EPOCH_FIELDS: + return _format_local_epoch(raw_value) + + value = str(raw_value) if raw_value not in ("", None) else "" + if value and sensitive: + value = mask_sensitive(value) + return value + + +def _format_local_epoch(raw_value: object) -> str: + if raw_value in ("", None): + return "" + + if isinstance(raw_value, int): + epoch = raw_value + elif isinstance(raw_value, str): + try: + epoch = int(raw_value) + except ValueError: + return raw_value + else: + return str(raw_value) + + if epoch <= 0: + return "" + + try: + dt = datetime.fromtimestamp(epoch).astimezone() + except (OSError, OverflowError, ValueError): + return str(raw_value) + + display = dt.strftime("%Y-%m-%d %H:%M:%S") + offset = dt.strftime("%z") + if offset: + return "{} (UTC{}:{})".format(display, offset[:3], offset[3:]) + timezone_name = dt.tzname() + if timezone_name: + return "{} ({})".format(display, timezone_name) + return display + + +@contextmanager +def _oauth_escape_cancel_event(): + cancel_event = threading.Event() + stop_event = threading.Event() + listener: threading.Thread | None = None + fd: int | None = None + old_terminal_settings = None + + if sys.stdin is None or not sys.stdin.isatty(): + yield cancel_event + return + + if _IS_WIN32: + listener = threading.Thread(target=_watch_oauth_escape_win, args=(cancel_event, stop_event), daemon=True) + else: + import termios + import tty + + try: + fd = sys.stdin.fileno() + old_terminal_settings = termios.tcgetattr(fd) + tty.setcbreak(fd) + except Exception: + yield cancel_event + return + listener = threading.Thread(target=_watch_oauth_escape_posix, args=(fd, cancel_event, stop_event), daemon=True) + + listener.start() + try: + yield cancel_event + finally: + stop_event.set() + listener.join(timeout=0.2) + if fd is not None and old_terminal_settings is not None: + import termios + + termios.tcsetattr(fd, termios.TCSADRAIN, old_terminal_settings) + + +def _watch_oauth_escape_win(cancel_event: threading.Event, stop_event: threading.Event) -> None: + try: + msvcrt = _get_msvcrt() + while not stop_event.wait(0.05): + if not msvcrt.kbhit(): + continue + key = msvcrt.getch()[0] + if key in (0x00, 0xE0): + if msvcrt.kbhit(): + msvcrt.getch() + continue + if key in (3, 27): + cancel_event.set() + return + except (Exception, KeyboardInterrupt): + cancel_event.set() + + +def _watch_oauth_escape_posix(fd: int, cancel_event: threading.Event, stop_event: threading.Event) -> None: + import select as select_mod + + while not stop_event.is_set(): + try: + ready, _, _ = select_mod.select([fd], [], [], 0.05) + except Exception: + return + if not ready: + continue + + try: + key = os.read(fd, 1) + except OSError: + return + + if key == b"\x03": + cancel_event.set() + return + if key != b"\x1b": + continue + + # Treat lone Esc as cancel, but consume escape sequences such as arrow keys. + try: + ready, _, _ = select_mod.select([fd], [], [], 0.03) + if ready: + os.read(fd, 4096) + continue + except OSError: + return + + cancel_event.set() + return + + +def _aliyun_oauth_login_flow(existing_cred: "AliyunCredential | None") -> str | None | _BackSentinel: + from iac_code.services.providers.aliyun import AliyunCredential, AliyunCredentials + from iac_code.services.providers.aliyun_oauth import ( + AliyunOAuthCancelledError, + AliyunOAuthClient, + AliyunOAuthError, + get_oauth_site, + oauth_site_options, + run_browser_oauth_flow, + ) + + site_options = oauth_site_options() + site_label_by_type = { + "CN": _("China"), + "INTL": _("International"), + } + site_idx = _select(_("Choose site type"), [site_label_by_type[site_type] for site_type, _label in site_options]) + if site_idx is None: + return _BACK + + site_type = site_options[site_idx][0] + site = get_oauth_site(site_type) + client = AliyunOAuthClient(site) + + try: + with _oauth_escape_cancel_event() as cancel_event: + token = run_browser_oauth_flow(site_type, oauth_client=client, cancel_event=cancel_event) + sts = client.exchange_access_token_for_sts(token.access_token) + except AliyunOAuthCancelledError: + return _BACK + except AliyunOAuthError as exc: + return _("Alibaba Cloud OAuth login failed: {error}").format(error=str(exc)) + + credential = AliyunCredential( + mode="OAuth", + region_id=existing_cred.region_id if existing_cred else "cn-hangzhou", + oauth_site_type=site_type, + oauth_access_token=token.access_token, + oauth_refresh_token=token.refresh_token, + oauth_access_token_expire=token.access_token_expire, + oauth_refresh_token_expire=token.refresh_token_expire, + access_key_id=sts.access_key_id, + access_key_secret=sts.access_key_secret, + sts_token=sts.sts_token, + sts_expiration=sts.sts_expiration, + ) + AliyunCredentials.save(credential) + return _("Configured: Alibaba Cloud OAuth credentials saved") + + def _aliyun_credential_flow() -> str | None | _BackSentinel: """Configure Aliyun credentials with type selection.""" from iac_code.services.providers.aliyun import ( CREDENTIAL_MODES, - MODE_DISPLAY_NAMES, MODE_FIELDS, AliyunCredential, AliyunCredentials, @@ -1222,7 +1446,7 @@ def _aliyun_credential_flow() -> str | None | _BackSentinel: # action_idx == 0: continue to reconfigure # Select credential mode - mode_options = [_(MODE_DISPLAY_NAMES[m]) for m in CREDENTIAL_MODES] + mode_options = [_aliyun_credential_mode_label(mode) for mode in CREDENTIAL_MODES] default_mode_idx = 0 if existing_cred and existing_cred.mode in CREDENTIAL_MODES: default_mode_idx = CREDENTIAL_MODES.index(existing_cred.mode) @@ -1234,6 +1458,12 @@ def _aliyun_credential_flow() -> str | None | _BackSentinel: return _BACK selected_mode = CREDENTIAL_MODES[mode_idx] + if selected_mode == "OAuth": + result = _aliyun_oauth_login_flow(existing_cred) + if result is _BACK: + continue + return result + mode_fields = MODE_FIELDS[selected_mode] # Collect field values 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 f593118..201a527 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:45+0800\n" +"POT-Creation-Date: 2026-06-02 21:26+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: de\n" @@ -20,26 +20,18 @@ msgstr "" #: src/iac_code/config.py:164 #, python-brace-format msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}" -msgstr "" -"Ungültiger IAC_CODE_PROVIDER-Wert: {!r}. Gültige Werte " -"(Groß-/Kleinschreibung wird ignoriert): {}" +msgstr "Ungültiger IAC_CODE_PROVIDER-Wert: {!r}. Gültige Werte (Groß-/Kleinschreibung wird ignoriert): {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "" -"Unix domain socket transport is not supported on Windows. Use --transport" -" http or --transport stdio instead." +msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." msgstr "" -"Unix-Domain-Socket-Transport wird unter Windows nicht unterstützt. " -"Verwenden Sie stattdessen --transport http oder --transport stdio." +"Unix-Domain-Socket-Transport wird unter Windows nicht unterstützt. Verwenden Sie stattdessen --transport http oder " +"--transport stdio." #: src/iac_code/acp/slash_registry.py:44 #, python-brace-format -msgid "" -"Command '/{cmd_name}' is not supported over ACP. Supported commands: " -"{supported}" -msgstr "" -"Der Befehl '/{cmd_name}' wird über ACP nicht unterstützt. Unterstützte " -"Befehle: {supported}" +msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" +msgstr "Der Befehl '/{cmd_name}' wird über ACP nicht unterstützt. Unterstützte Befehle: {supported}" #: src/iac_code/acp/slash_registry.py:58 #, python-brace-format @@ -57,12 +49,8 @@ msgstr "Nichts zu komprimieren: Die Konversation ist leer." #: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "" -"Conversation too short to compact: all messages are within the recent " -"{turns}-turn preservation window." -msgstr "" -"Konversation zu kurz zum Komprimieren: Alle Nachrichten liegen im " -"Erhaltungsfenster der letzten {turns} Runden." +msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." +msgstr "Konversation zu kurz zum Komprimieren: Alle Nachrichten liegen im Erhaltungsfenster der letzten {turns} Runden." #: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." @@ -70,12 +58,8 @@ msgstr "Komprimierung fehlgeschlagen. Details finden Sie in den Protokollen." #: src/iac_code/acp/slash_registry.py:85 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent} reduction)." -" Context usage: {usage}" -msgstr "" -"Kontext komprimiert: {original} → {compacted} Tokens ({percent} " -"Reduzierung). Kontextnutzung: {usage}" +msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" +msgstr "Kontext komprimiert: {original} → {compacted} Tokens ({percent} Reduzierung). Kontextnutzung: {usage}" #: src/iac_code/acp/slash_registry.py:99 #, python-brace-format @@ -112,8 +96,8 @@ msgstr "Verwendung: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "Der Speicher-Manager ist nicht verfügbar." -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "Zugriff verweigert." @@ -130,8 +114,7 @@ msgstr "Erkunden" msgid "Plan" msgstr "Plan" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 -#: src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "Agent" @@ -183,17 +166,14 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-" -"code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " Lösung: Führen Sie iac-code aus und geben Sie /auth ein\n" -" oder setzen Sie die Umgebungsvariable: IAC_CODE_API_KEY=" -"\n" -" Dokumentation: https://aliyun.github.io/iac-" -"code/de/docs/configuration/authentication\n" +" oder setzen Sie die Umgebungsvariable: IAC_CODE_API_KEY=\n" +" Dokumentation: https://aliyun.github.io/iac-code/de/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -206,9 +186,7 @@ msgstr "Git for Windows wird über npmmirror installiert..." #: src/iac_code/cli/install_git_bash.py:59 msgid "powershell.exe was not found on PATH; cannot run installer." -msgstr "" -"powershell.exe wurde nicht im PATH gefunden; das Installationsprogramm " -"kann nicht ausgeführt werden." +msgstr "powershell.exe wurde nicht im PATH gefunden; das Installationsprogramm kann nicht ausgeführt werden." #: src/iac_code/cli/install_git_bash.py:66 #, python-brace-format @@ -217,12 +195,11 @@ msgstr "Installation fehlgeschlagen (PowerShell beendet mit Code {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may " -"have been cancelled or the installer used a non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " +"non-standard path." msgstr "" -"Installer wurde beendet, aber bash.exe wurde nicht an gängigen Orten " -"gefunden; UAC wurde möglicherweise abgebrochen oder der Installer hat " -"einen nicht standardmäßigen Pfad verwendet." +"Installer wurde beendet, aber bash.exe wurde nicht an gängigen Orten gefunden; UAC wurde möglicherweise abgebrochen " +"oder der Installer hat einen nicht standardmäßigen Pfad verwendet." #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -245,14 +222,9 @@ msgstr "Git for Windows über den npmmirror-Spiegel installieren (nur Windows)." msgid "YAML config file containing A2A client options" msgstr "YAML-Konfigurationsdatei mit A2A-Client-Optionen" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 -#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "" -"A2A client dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" -msgstr "" -"A2A-Client-Abhängigkeiten fehlen. Installieren mit: pip install 'iac-" -"code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgstr "A2A-Client-Abhängigkeiten fehlen. Installieren mit: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 msgid "LLM model to use" @@ -270,8 +242,7 @@ msgstr "Ausgabeformat: text, json, stream-json" msgid "Maximum agent turns in headless mode" msgstr "Maximale Agent-Runden im Headless-Modus" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 -#: src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "Debug-Protokollierung aktivieren" @@ -296,20 +267,12 @@ msgid "Install completion for the current shell." msgstr "Vervollständigung für die aktuelle Shell installieren." #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "" -"Show completion for the current shell, to copy it or customize the " -"installation." -msgstr "" -"Vervollständigung für die aktuelle Shell anzeigen, zum Kopieren oder " -"Anpassen der Installation." +msgid "Show completion for the current shell, to copy it or customize the installation." +msgstr "Vervollständigung für die aktuelle Shell anzeigen, zum Kopieren oder Anpassen der Installation." #: src/iac_code/cli/main.py:110 -msgid "" -"Comma-separated tool permission patterns to allow, e.g. 'bash(git " -"*),write_file'" -msgstr "" -"Durch Komma getrennte Tool-Berechtigungsmuster zum Erlauben, z.B. " -"'bash(git *),write_file'*),write_file'" +msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" +msgstr "Durch Komma getrennte Tool-Berechtigungsmuster zum Erlauben, z.B. 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 msgid "Comma-separated tool permission patterns to deny" @@ -318,15 +281,14 @@ msgstr "Durch Komma getrennte Tool-Berechtigungsmuster zum Verweigern" #: src/iac_code/cli/main.py:120 msgid "Permission mode: default, accept_edits, bypass_permissions, dont_ask" msgstr "" -"Permission mode: default, accept_edits, bypass_permissions, " -"dont_askBerechtigungsmodus: default, accept_edits, bypass_permissions, " -"dont_ask" +"Permission mode: default, accept_edits, bypass_permissions, dont_askBerechtigungsmodus: default, accept_edits, " +"bypass_permissions, dont_ask" #: src/iac_code/cli/main.py:151 msgid "Error: --resume and --continue cannot be used together." msgstr "" -"Error: --resume and --continue cannot be used together.Fehler: --resume " -"und --continue können nicht gemeinsam verwendet werden." +"Error: --resume and --continue cannot be used together.Fehler: --resume und --continue können nicht gemeinsam " +"verwendet werden." #: src/iac_code/cli/main.py:163 #, python-brace-format @@ -358,45 +320,27 @@ msgid "YAML config file for A2A server options" msgstr "YAML-Konfigurationsdatei für A2A-Server-Optionen" #: src/iac_code/cli/main.py:584 -msgid "" -"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " -"not a registered A2A port." -msgstr "" -"HTTP-Serverport. 41242 ist der von Gemini CLI inspirierte iac-code-" -"Standard, kein registrierter A2A-Port." +msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." +msgstr "HTTP-Serverport. 41242 ist der von Gemini CLI inspirierte iac-code-Standard, kein registrierter A2A-Port." #: src/iac_code/cli/main.py:589 -msgid "" -"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " -"redis-streams" -msgstr "" -"A2A-Transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc oder " -"redis-streams" +msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" +msgstr "A2A-Transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc oder redis-streams" #: src/iac_code/cli/main.py:595 -msgid "" -"Expose A2A thinking signal types; repeat for multiple. Values: raw-" -"thinking, tool-trace." -msgstr "" -"Legt A2A-Thinking-Signaltypen offen; fuer mehrere Werte wiederholen. " -"Werte: raw-thinking, tool-trace." +msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." +msgstr "Legt A2A-Thinking-Signaltypen offen; fuer mehrere Werte wiederholen. Werte: raw-thinking, tool-trace." #: src/iac_code/cli/main.py:646 -msgid "" -"A2A server dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" -msgstr "" -"A2A-Server-Abhängigkeiten fehlen. Installieren mit: pip install 'iac-" -"code[a2a]'" +msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgstr "A2A-Server-Abhängigkeiten fehlen. Installieren mit: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "Sendet einen Prompt an einen A2A-JSON-RPC-Endpunkt." -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 -#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 -#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "URL des A2A-JSON-RPC-Endpunkts" @@ -421,48 +365,33 @@ msgstr "Arbeitsverzeichnis-Metadaten, die mit der Anfrage gesendet werden" msgid "A2A context ID to continue" msgstr "Fortzusetzende A2A-Kontext-ID" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 -#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 -#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 -#: src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "Bearer-Token für A2A-HTTP-Anfragen" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 -#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 -#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 -#: src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "Basic-Auth-Benutzername für A2A-HTTP-Anfragen" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 -#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 -#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 -#: src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "Basic-Auth-Passwort für A2A-HTTP-Anfragen" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 -#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 -#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 -#: src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "API-Schlüssel für A2A-HTTP-Anfragen" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 -#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 -#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 -#: src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "HTTP-Header-Name für den A2A-API-Schlüssel" @@ -498,10 +427,8 @@ msgstr "Basis-URL des A2A-Agenten" msgid "Get an A2A task." msgstr "Eine A2A-Aufgabe abrufen." -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 -#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 -#: src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "A2A-Aufgaben-ID" @@ -549,8 +476,7 @@ msgstr "A2A-Aufgaben-Ereignisstrom abonnieren." msgid "Create an A2A task push notification config." msgstr "Eine Push-Benachrichtigungskonfiguration für eine A2A-Aufgabe erstellen." -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 -#: src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "Push-Konfigurations-ID" @@ -674,227 +600,295 @@ msgstr "Skills verwalten" msgid "Show current session status" msgstr "Aktuellen Sitzungsstatus anzeigen" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navigieren" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:530 -#: src/iac_code/commands/auth.py:562 src/iac_code/commands/auth.py:569 -#: src/iac_code/commands/auth.py:604 src/iac_code/commands/auth.py:611 -#: src/iac_code/commands/auth.py:632 src/iac_code/commands/auth.py:1107 -#: src/iac_code/commands/auth.py:1319 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Bestätigen" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:528 -#: src/iac_code/commands/auth.py:530 src/iac_code/commands/auth.py:562 -#: src/iac_code/commands/auth.py:569 src/iac_code/commands/auth.py:604 -#: src/iac_code/commands/auth.py:611 src/iac_code/commands/auth.py:632 -#: src/iac_code/commands/auth.py:1107 src/iac_code/commands/auth.py:1217 -#: src/iac_code/commands/auth.py:1319 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "Zurück" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Keep" msgstr "Behalten" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Re-enter" msgstr "Erneut eingeben" -#: src/iac_code/commands/auth.py:741 src/iac_code/commands/auth.py:853 -#: src/iac_code/commands/auth.py:911 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:946 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (aktuell)" -#: src/iac_code/commands/auth.py:744 +#: src/iac_code/commands/auth.py:752 msgid "Custom model..." msgstr "Benutzerdefiniertes Modell …" -#: src/iac_code/commands/auth.py:747 +#: src/iac_code/commands/auth.py:755 #, python-brace-format msgid "Select model for {provider}" msgstr "Modell für {provider} auswählen" -#: src/iac_code/commands/auth.py:749 +#: src/iac_code/commands/auth.py:757 msgid "Select model" msgstr "Modell auswählen" -#: src/iac_code/commands/auth.py:757 +#: src/iac_code/commands/auth.py:765 msgid "Enter custom model name: " msgstr "Benutzerdefinierten Modellnamen eingeben: " -#: src/iac_code/commands/auth.py:783 +#: src/iac_code/commands/auth.py:791 msgid "Error: console not available" msgstr "Fehler: Konsole nicht verfügbar" -#: src/iac_code/commands/auth.py:810 +#: src/iac_code/commands/auth.py:818 msgid "Configure LLM Provider" msgstr "LLM-Anbieter konfigurieren" -#: src/iac_code/commands/auth.py:811 +#: src/iac_code/commands/auth.py:819 msgid "Configure IaC Cloud Service" msgstr "IaC-Cloud-Dienst konfigurieren" -#: src/iac_code/commands/auth.py:813 +#: src/iac_code/commands/auth.py:821 msgid "Select configuration type" msgstr "Konfigurationstyp auswählen" -#: src/iac_code/commands/auth.py:815 src/iac_code/commands/auth.py:971 -#: src/iac_code/commands/auth.py:987 src/iac_code/commands/auth.py:1066 -#: src/iac_code/commands/auth.py:1258 src/iac_code/commands/auth.py:1299 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 +#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "Authentifizierung abgebrochen" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:951 -#: src/iac_code/commands/auth.py:1041 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "Anbieter auswählen — {group}" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:909 -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "Drittanbieter" -#: src/iac_code/commands/auth.py:867 +#: src/iac_code/commands/auth.py:875 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:868 src/iac_code/commands/auth.py:1019 +#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 msgid "Configured" msgstr "Konfiguriert" -#: src/iac_code/commands/auth.py:923 +#: src/iac_code/commands/auth.py:931 msgid "Select provider" msgstr "Anbieter auswählen" -#: src/iac_code/commands/auth.py:964 +#: src/iac_code/commands/auth.py:972 #, python-brace-format msgid "Configure {provider}" msgstr "{provider} konfigurieren" -#: src/iac_code/commands/auth.py:980 +#: src/iac_code/commands/auth.py:988 #, python-brace-format msgid "Enter API key for {provider}" msgstr "API-Key für {provider} eingeben" -#: src/iac_code/commands/auth.py:1018 +#: src/iac_code/commands/auth.py:1026 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:1027 src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1028 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1029 +#: src/iac_code/commands/auth.py:1037 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1030 +#: src/iac_code/commands/auth.py:1038 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1031 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1032 +#: src/iac_code/commands/auth.py:1040 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1033 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1034 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1035 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1037 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1039 +#: src/iac_code/commands/auth.py:1047 msgid "Local" msgstr "Lokal" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1048 msgid "Compatible" msgstr "Kompatibel" -#: src/iac_code/commands/auth.py:1057 +#: src/iac_code/commands/auth.py:1065 msgid "Select Cloud Provider" msgstr "Cloud-Anbieter auswählen" -#: src/iac_code/commands/auth.py:1073 +#: src/iac_code/commands/auth.py:1081 msgid "Credential" msgstr "Anmeldedaten" -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 -#: src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 +#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Region" -#: src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1084 msgid "Configure Alibaba Cloud" msgstr "Alibaba Cloud konfigurieren" -#: src/iac_code/commands/auth.py:1178 +#: src/iac_code/commands/auth.py:1186 msgid "Current configuration" msgstr "Aktuelle Konfiguration" -#: src/iac_code/commands/auth.py:1180 +#: src/iac_code/commands/auth.py:1188 msgid "Mode" msgstr "Modus" -#: src/iac_code/commands/auth.py:1187 +#: src/iac_code/commands/auth.py:1194 msgid "(not set)" msgstr "(nicht gesetzt)" -#: src/iac_code/commands/auth.py:1204 +#: src/iac_code/commands/auth.py:1203 +msgid "AccessKey" +msgstr "AccessKey" + +#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +msgid "STS Token" +msgstr "STS-Token" + +#: src/iac_code/commands/auth.py:1207 +msgid "RAM Role" +msgstr "RAM-Rolle" + +#: src/iac_code/commands/auth.py:1209 +msgid "OAuth Login (Browser)" +msgstr "OAuth-Anmeldung (Browser)" + +#: src/iac_code/commands/auth.py:1215 +msgid "AccessKey ID" +msgstr "AccessKey-ID" + +#: src/iac_code/commands/auth.py:1216 +msgid "AccessKey Secret" +msgstr "AccessKey-Secret" + +#: src/iac_code/commands/auth.py:1218 +msgid "RAM Role ARN" +msgstr "RAM-Rollen-ARN" + +#: src/iac_code/commands/auth.py:1219 +msgid "Session Name" +msgstr "Sitzungsname" + +#: src/iac_code/commands/auth.py:1220 +msgid "OAuth Site Type" +msgstr "OAuth-Standorttyp" + +#: src/iac_code/commands/auth.py:1221 +msgid "OAuth Access Token" +msgstr "OAuth-Zugriffstoken" + +#: src/iac_code/commands/auth.py:1222 +msgid "OAuth Refresh Token" +msgstr "OAuth-Refresh-Token" + +#: src/iac_code/commands/auth.py:1223 +msgid "OAuth Access Token Expire" +msgstr "Ablaufzeit des OAuth-Zugriffstokens" + +#: src/iac_code/commands/auth.py:1224 +msgid "OAuth Refresh Token Expire" +msgstr "Ablaufzeit des OAuth-Refresh-Tokens" + +#: src/iac_code/commands/auth.py:1225 +msgid "STS Expiration" +msgstr "STS-Ablaufzeit" + +#: src/iac_code/commands/auth.py:1382 +msgid "China" +msgstr "China" + +#: src/iac_code/commands/auth.py:1383 +msgid "International" +msgstr "International" + +#: src/iac_code/commands/auth.py:1385 +msgid "Choose site type" +msgstr "Standorttyp auswählen" + +#: src/iac_code/commands/auth.py:1400 +#, python-brace-format +msgid "Alibaba Cloud OAuth login failed: {error}" +msgstr "Alibaba Cloud OAuth-Anmeldung fehlgeschlagen: {error}" + +#: src/iac_code/commands/auth.py:1416 +msgid "Configured: Alibaba Cloud OAuth credentials saved" +msgstr "Konfiguriert: Alibaba Cloud-OAuth-Anmeldedaten gespeichert" + +#: src/iac_code/commands/auth.py:1428 msgid "Configure Alibaba Cloud credentials" msgstr "Alibaba Cloud-Anmeldedaten konfigurieren" -#: src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1441 msgid "Reconfigure credential" msgstr "Anmeldedaten neu konfigurieren" -#: src/iac_code/commands/auth.py:1230 +#: src/iac_code/commands/auth.py:1454 msgid "Select credential type" msgstr "Anmeldedatentyp auswählen" -#: src/iac_code/commands/auth.py:1280 +#: src/iac_code/commands/auth.py:1510 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "Konfiguriert: Alibaba Cloud-Anmeldedaten unter ~/.iac-code gespeichert" -#: src/iac_code/commands/auth.py:1287 +#: src/iac_code/commands/auth.py:1517 msgid "Configure Alibaba Cloud region" msgstr "Alibaba Cloud-Region konfigurieren" -#: src/iac_code/commands/auth.py:1313 +#: src/iac_code/commands/auth.py:1543 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Konfiguriert: Alibaba Cloud-Region unter ~/.iac-code gespeichert" @@ -912,12 +906,8 @@ msgstr "Keine aktive Agent-Schleife." #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent_display} " -"reduction). Context usage: {usage_display}" -msgstr "" -"Kontext komprimiert: {original} → {compacted} Tokens (Reduzierung " -"{percent_display}). Kontextnutzung: {usage_display}" +msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" +msgstr "Kontext komprimiert: {original} → {compacted} Tokens (Reduzierung {percent_display}). Kontextnutzung: {usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -927,8 +917,7 @@ msgstr "Der Befehl debug erfordert einen Kontext." msgid "No active session." msgstr "Keine aktive Sitzung." -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 -#: src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "Keine konfigurierten Anbieter. Führen Sie zuerst /auth aus." @@ -1022,12 +1011,8 @@ msgstr "Erinnerung '{name}' gelöscht." #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "" -"Model is managed by '{source}'. To change model, modify it in {source} or" -" switch provider via /auth." -msgstr "" -"Das Modell wird von '{source}' verwaltet. Ändern Sie es in {source} oder " -"wechseln Sie den Provider über /auth." +msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." +msgstr "Das Modell wird von '{source}' verwaltet. Ändern Sie es in {source} oder wechseln Sie den Provider über /auth." #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1093,8 +1078,7 @@ msgstr "Sitzung" msgid "Provider" msgstr "Anbieter" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 -#: src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 msgid "not configured" msgstr "nicht konfiguriert" @@ -1195,48 +1179,39 @@ msgstr "Abgebrochen!" #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." msgstr "" -"Cannot determine provider for model: {model}. Run /auth to " -"configure.Cannot determine provider for model: {model}. Run /auth to " -"configure.Anbieter für Modell {model} kann nicht ermittelt werden. Führen" -" Sie /auth aus, um die Konfiguration vorzunehmen." +"Cannot determine provider for model: {model}. Run /auth to configure.Cannot determine provider for model: {model}. " +"Run /auth to configure.Anbieter für Modell {model} kann nicht ermittelt werden. Führen Sie /auth aus, um die " +"Konfiguration vorzunehmen." #: src/iac_code/providers/manager.py:95 #, python-brace-format msgid "Unknown provider key: '{key}'. Run /auth to configure." -msgstr "" -"Unbekannter Anbieterschlüssel: '{key}'. Führen Sie /auth aus, um die " -"Konfiguration vorzunehmen." +msgstr "Unbekannter Anbieterschlüssel: '{key}'. Führen Sie /auth aus, um die Konfiguration vorzunehmen." #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "" -"No API key configured for provider '{provider}' (model: {model}). Run " -"/auth to configure." +msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." msgstr "" -"Kein API-Schlüssel für Anbieter '{provider}' konfiguriert (Modell: " -"{model}). Führen Sie /auth aus, um die Konfiguration vorzunehmen." +"Kein API-Schlüssel für Anbieter '{provider}' konfiguriert (Modell: {model}). Führen Sie /auth aus, um die " +"Konfiguration vorzunehmen." #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct " -"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " -"suffix (e.g. {base_url}/v1)." +"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " +"endpoints require a /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"Die API hat keine Daten zurückgegeben. Prüfen Sie, ob Ihre API Base URL " -"korrekt ist (aktuell: {base_url}). Viele OpenAI-kompatible Endpunkte " -"erfordern ein /v1-Suffix (z. B. {base_url}/v1)." +"Die API hat keine Daten zurückgegeben. Prüfen Sie, ob Ihre API Base URL korrekt ist (aktuell: {base_url}). Viele " +"OpenAI-kompatible Endpunkte erfordern ein /v1-Suffix (z. B. {base_url}/v1)." #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is " -"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" -" /v1 suffix (e.g. {base_url}/v1)." +"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" +"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"Die API hat eine ungültige Antwort zurückgegeben. Prüfen Sie, ob Ihre API" -" Base URL korrekt ist (aktuell: {base_url}). Viele OpenAI-kompatible " -"Endpunkte erfordern ein /v1-Suffix (z. B. {base_url}/v1)." +"Die API hat eine ungültige Antwort zurückgegeben. Prüfen Sie, ob Ihre API Base URL korrekt ist (aktuell: {base_url})." +" Viele OpenAI-kompatible Endpunkte erfordern ein /v1-Suffix (z. B. {base_url}/v1)." #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1317,36 +1292,131 @@ msgstr "Anthropic-kompatibel" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " -"support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " -"mode (remove 'llm_source: qwenpaw' from settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " +"settings.yml)." msgstr "" -"[QwenPaw-Modus] Unbekannter Provider '{provider_id}'. iac-code " -"unterstützt diesen Provider nicht.\n" +"[QwenPaw-Modus] Unbekannter Provider '{provider_id}'. iac-code unterstützt diesen Provider nicht.\n" "Unterstützte QwenPaw-Provider-IDs: {supported_ids}\n" -"Lösung: Wechseln Sie in QwenPaw zu einem unterstützten Provider, oder " -"deaktivieren Sie den QwenPaw-Modus (entfernen Sie 'llm_source: qwenpaw' " -"aus settings.yml)." +"Lösung: Wechseln Sie in QwenPaw zu einem unterstützten Provider, oder deaktivieren Sie den QwenPaw-Modus (entfernen " +"Sie 'llm_source: qwenpaw' aus settings.yml)." #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "Ungültiges --permission-mode {!r}. Gültige Werte: {}" -#: src/iac_code/services/permissions/pipeline.py:54 -#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "{} erlauben?" +#: src/iac_code/services/providers/aliyun.py:144 +msgid "Alibaba Cloud OAuth site is missing." +msgstr "Die Alibaba Cloud-OAuth-Site fehlt." + +#: src/iac_code/services/providers/aliyun.py:150 +msgid "Alibaba Cloud OAuth refresh token is missing." +msgstr "Das Alibaba Cloud-OAuth-Refresh-Token fehlt." + +#: src/iac_code/services/providers/aliyun.py:158 +msgid "Alibaba Cloud OAuth access token is missing." +msgstr "Das Alibaba Cloud-OAuth-Zugriffstoken fehlt." + +#: src/iac_code/services/providers/aliyun_oauth.py:83 +msgid "Run /auth and choose OAuth Login (Browser)." +msgstr "Führen Sie /auth aus und wählen Sie OAuth-Anmeldung (Browser)." + +#: src/iac_code/services/providers/aliyun_oauth.py:106 +#, python-brace-format +msgid "Unknown Aliyun OAuth site: {site_type}" +msgstr "Unbekannte Aliyun-OAuth-Site: {site_type}" + +#: src/iac_code/services/providers/aliyun_oauth.py:164 +msgid "Not found" +msgstr "Nicht gefunden" + +#: src/iac_code/services/providers/aliyun_oauth.py:170 +msgid "invalid state" +msgstr "ungültiger Status" + +#: src/iac_code/services/providers/aliyun_oauth.py:171 +msgid "Invalid state" +msgstr "Ungültiger Status" + +#: src/iac_code/services/providers/aliyun_oauth.py:176 +msgid "code not found" +msgstr "Autorisierungscode nicht gefunden" + +#: src/iac_code/services/providers/aliyun_oauth.py:177 +msgid "Authorization code not found" +msgstr "Autorisierungscode nicht gefunden" + +#: src/iac_code/services/providers/aliyun_oauth.py:181 +msgid "Authorization successful. You can close this window." +msgstr "Autorisierung erfolgreich. Sie können dieses Fenster schließen." + +#: src/iac_code/services/providers/aliyun_oauth.py:212 +#, python-brace-format +msgid "No available callback port in range {start}-{end}" +msgstr "Kein verfügbarer Callback-Port im Bereich {start}-{end}" + +#: src/iac_code/services/providers/aliyun_oauth.py:227 +msgid "OAuth login cancelled." +msgstr "OAuth-Anmeldung abgebrochen." + +#: src/iac_code/services/providers/aliyun_oauth.py:278 +msgid "Open in your browser:" +msgstr "Im Browser öffnen:" + +#: src/iac_code/services/providers/aliyun_oauth.py:299 +msgid "Waiting for browser authorization" +msgstr "Warten auf Browserautorisierung" + +#: src/iac_code/services/providers/aliyun_oauth.py:300 +msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." +msgstr "1. Der Browser kann official-cli anzeigen; dies ist die offizielle CLI-OAuth-Anwendung von Alibaba Cloud." + +#: src/iac_code/services/providers/aliyun_oauth.py:302 +msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgstr "" +"2. Falls eine Zuweisung erforderlich ist, weisen Sie den angemeldeten RAM-Benutzer oder die RAM-Rolle zu. " +"Benutzergruppen werden nicht unterstützt." + +#: src/iac_code/services/providers/aliyun_oauth.py:306 +msgid "" +"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " +"out of Alibaba Cloud and sign in again." +msgstr "" +"3. Schließen Sie nach der Zuweisung die alte Autorisierungsseite und führen Sie OAuth-Anmeldung (Browser) erneut aus." +" Falls es weiterhin fehlschlägt, melden Sie sich bei Alibaba Cloud ab und wieder an." + +#: src/iac_code/services/providers/aliyun_oauth.py:310 +msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgstr "" +"4. STS-Anmeldeinformationen werden nach Möglichkeit aktualisiert, bis Alibaba Cloud sie ablaufen lässt. Wenn die " +"Aktualisierung fehlschlägt, führen Sie /auth erneut aus." + +#: src/iac_code/services/providers/aliyun_oauth.py:313 +msgid "Press Esc to cancel while waiting." +msgstr "Drücken Sie Esc, um das Warten abzubrechen." + +#: src/iac_code/services/providers/aliyun_oauth.py:321 +msgid "" +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" +" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " +"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +msgstr "" +"Zeitüberschreitung beim Warten auf den OAuth-Callback. Wenn Alibaba Cloud Sie aufgefordert hat, die Anwendung " +"official-cli zuzuweisen, weisen Sie sie genau dem aktuell angemeldeten RAM-Benutzer oder der RAM-Rolle zu. " +"Benutzergruppen werden nicht unterstützt. Schließen Sie danach die alte Autorisierungsseite, melden Sie sich bei " +"Alibaba Cloud ab und bei Bedarf wieder an, und führen Sie /auth aus, um erneut OAuth-Anmeldung (Browser) zu wählen." + #: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." -msgstr "" -"Skill '{name}' ist deaktiviert. Führen Sie /skills aus, um ihn zu " -"aktivieren." +msgstr "Skill '{name}' ist deaktiviert. Führen Sie /skills aus, um ihn zu aktivieren." #: src/iac_code/skills/skill_tool.py:137 #, python-brace-format @@ -1363,12 +1433,8 @@ msgid "Skill disabled: {name}" msgstr "Skill deaktiviert: {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "" -"Review changed code for reuse, quality, and efficiency, then fix issues " -"found." -msgstr "" -"Geänderten Code auf Wiederverwendbarkeit, Qualität und Effizienz prüfen " -"und gefundene Probleme beheben." +msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." +msgstr "Geänderten Code auf Wiederverwendbarkeit, Qualität und Effizienz prüfen und gefundene Probleme beheben." #: src/iac_code/tools/edit_file.py:116 msgid "Edit" @@ -1466,9 +1532,8 @@ msgstr "Die URL darf nicht leer sein." #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: " -"{url}Ungültige URL: Schema fehlt (z. B. http:// oder https://). Erhalten:" -" {url}" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Ungültige URL: Schema fehlt (z. B. http:// oder " +"https://). Erhalten: {url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1577,8 +1642,7 @@ msgstr "Übereinstimmende Ablehnungsregel(n): {}" msgid "matched ask rule(s): {}" msgstr "Übereinstimmende Abfrageregel(n): {}" -#: src/iac_code/tools/bash/permissions.py:154 -#: src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "Übereinstimmende Erlaubnisregel(n): {}" @@ -1635,8 +1699,7 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "{action} wird aufgerufen …" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 -#: src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Aufruf erfolgreich" @@ -1649,8 +1712,7 @@ msgstr "Antwort empfangen ({count} Zeilen)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 -#: src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "{action} wird ausgeführt …" @@ -1795,11 +1857,11 @@ msgstr "IMPORT ABGESCHLOSSEN" msgid "IMPORT_FAILED" msgstr "IMPORT FEHLGESCHLAGEN" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:171 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:172 msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "Aufruf erfolgreich (RequestId: {request_id})" @@ -1856,9 +1918,8 @@ msgstr "{count} Dokumente gefunden (gesamt {total})" #: src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py:143 msgid "Use web_fetch tool to read full document content if needed." msgstr "" -"Use web_fetch tool to read full document content if needed.Use web_fetch " -"tool to read full document content if needed.Verwenden Sie bei Bedarf das" -" Tool web_fetch für den vollständigen Dokumentinhalt." +"Use web_fetch tool to read full document content if needed.Use web_fetch tool to read full document content if " +"needed.Verwenden Sie bei Bedarf das Tool web_fetch für den vollständigen Dokumentinhalt." #: src/iac_code/tools/cloud/aliyun/ros_stack.py:143 msgid "ROS Stack" @@ -1891,20 +1952,12 @@ msgstr "JSON-Syntaxfehler in der Vorlage (Zeile {line}, Spalte {col}): {msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "" -"Template {fmt} parse result is not an object (dict), please check the " -"template format" -msgstr "" -"Das {fmt}-Parseergebnis der Vorlage ist kein Objekt (dict), bitte " -"überprüfen Sie das Vorlagenformat" +msgid "Template {fmt} parse result is not an object (dict), please check the template format" +msgstr "Das {fmt}-Parseergebnis der Vorlage ist kein Objekt (dict), bitte überprüfen Sie das Vorlagenformat" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "" -"Template is missing ROSTemplateFormatVersion (ROS templates must include " -"this field, e.g. '2015-09-01')" -msgstr "" -"In der Vorlage fehlt ROSTemplateFormatVersion (ROS-Vorlagen müssen dieses" -" Feld enthalten, z.B. '2015-09-01')" +msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" +msgstr "In der Vorlage fehlt ROSTemplateFormatVersion (ROS-Vorlagen müssen dieses Feld enthalten, z.B. '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" @@ -1917,12 +1970,8 @@ msgstr "Resources muss ein Objekt (dict) sein, aktueller Typ ist {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "" -"Resource '{name}' definition must be an object (dict), current type is " -"{type}" -msgstr "" -"Die Definition der Ressource '{name}' muss ein Objekt (dict) sein, " -"aktueller Typ ist {type}" +msgid "Resource '{name}' definition must be an object (dict), current type is {type}" +msgstr "Die Definition der Ressource '{name}' muss ein Objekt (dict) sein, aktueller Typ ist {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 #, python-brace-format @@ -1935,12 +1984,8 @@ msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" msgstr "Ressource '{name}' hat den falschen Typ '{wrong}', sollte '{correct}' sein" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "" -"Template structure validation found the following issues, please fix and " -"retry:" -msgstr "" -"Die Strukturvalidierung der Vorlage hat folgende Probleme gefunden, bitte" -" beheben und erneut versuchen:" +msgid "Template structure validation found the following issues, please fix and retry:" +msgstr "Die Strukturvalidierung der Vorlage hat folgende Probleme gefunden, bitte beheben und erneut versuchen:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 #, python-brace-format @@ -1976,14 +2021,12 @@ msgstr "Debug-Modus" msgid "Log file" msgstr "Protokolldatei" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 -#: src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "Nachgedacht für {seconds:.1f}s" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 -#: src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o zum Aufklappen)" @@ -2091,9 +2134,7 @@ msgstr "Jetzt aktualisieren" #: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." -msgstr "" -"Führt den angezeigten Aktualisierungsbefehl aus und beendet das Programm " -"bei Erfolg." +msgstr "Führt den angezeigten Aktualisierungsbefehl aus und beendet das Programm bei Erfolg." #: src/iac_code/ui/repl.py:499 msgid "Skip" @@ -2113,9 +2154,7 @@ msgstr "Dieses Update ausblenden, bis eine neuere Version verfügbar ist." #: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." -msgstr "" -"Der Aktualisierungsbefehl ist fehlgeschlagen. Es wird mit der aktuellen " -"Version fortgefahren." +msgstr "Der Aktualisierungsbefehl ist fehlgeschlagen. Es wird mit der aktuellen Version fortgefahren." #: src/iac_code/ui/repl.py:530 msgid "Update completed. Restart iac-code to continue." @@ -2142,8 +2181,8 @@ msgstr "Unbekannter Skill: ${name}. Tippe /, um Befehle und Skills aufzulisten." #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" -"Unknown command: /{name}. Type /help for available commands.Unbekannter " -"Befehl: /{name}. Geben Sie /help für verfügbare Befehle ein." +"Unknown command: /{name}. Type /help for available commands.Unbekannter Befehl: /{name}. Geben Sie /help für " +"verfügbare Befehle ein." #: src/iac_code/ui/repl.py:833 #, python-brace-format @@ -2190,12 +2229,10 @@ msgstr "(Befehl in die Zwischenablage kopiert)" #: src/iac_code/ui/repl.py:1451 #, python-brace-format -msgid "" -"Current model {model} does not support image input. Use /model to switch " -"to a vision-capable model." +msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." msgstr "" -"Das aktuelle Modell {model} unterstützt keine Bildeingabe. Verwenden Sie " -"/model, um zu einem Vision-fähigen Modell zu wechseln." +"Das aktuelle Modell {model} unterstützt keine Bildeingabe. Verwenden Sie /model, um zu einem Vision-fähigen Modell zu" +" wechseln." #: src/iac_code/ui/repl.py:1460 #, python-brace-format @@ -2203,12 +2240,8 @@ msgid "Image error: {err}" msgstr "Bildfehler: {err}" #: src/iac_code/ui/repl.py:1477 -msgid "" -"Failed to persist image to cache; it will only exist in memory for this " -"turn." -msgstr "" -"Bild konnte nicht im Cache gespeichert werden; es existiert nur im " -"Arbeitsspeicher für diesen Durchgang." +msgid "Failed to persist image to cache; it will only exist in memory for this turn." +msgstr "Bild konnte nicht im Cache gespeichert werden; es existiert nur im Arbeitsspeicher für diesen Durchgang." #: src/iac_code/ui/spinner.py:52 msgid "Processing" @@ -2398,12 +2431,8 @@ msgstr "{current} von {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "" -"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " -"cancel" -msgstr "" -"{count} Skills - Leertaste zum Umschalten, Enter zum Speichern, Tab zum " -"Sortieren, Esc zum Abbrechen" +msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" +msgstr "{count} Skills - Leertaste zum Umschalten, Enter zum Speichern, Tab zum Sortieren, Esc zum Abbrechen" #: src/iac_code/ui/dialogs/skills_picker.py:171 #, python-brace-format @@ -2489,12 +2518,8 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code unter Windows erfordert Git for Windows." #: src/iac_code/utils/platform.py:41 -msgid "" -"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " -"variable." -msgstr "" -"Falls installiert, aber nicht im PATH, setzen Sie die Umgebungsvariable " -"IAC_CODE_GIT_BASH_PATH." +msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." +msgstr "Falls installiert, aber nicht im PATH, setzen Sie die Umgebungsvariable IAC_CODE_GIT_BASH_PATH." #: src/iac_code/utils/platform.py:44 msgid "To install:" @@ -2505,12 +2530,8 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " Option 1 - winget (erfordert Zugang zu github.com):" #: src/iac_code/utils/platform.py:52 -msgid "" -" Option 2 - if you cannot reach github.com, run this to install via " -"npmmirror:" -msgstr "" -" Option 2 - falls Sie github.com nicht erreichen können, führen Sie dies" -" aus, um über npmmirror zu installieren:" +msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" +msgstr " Option 2 - falls Sie github.com nicht erreichen können, führen Sie dies aus, um über npmmirror zu installieren:" #~ msgid "toggle preview" #~ msgstr "Vorschau umschalten" @@ -2533,14 +2554,8 @@ msgstr "" #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "" -#~ "LLM provider is locked by '{source}'." -#~ " To change, modify llm_source in " -#~ "settings.yml." -#~ msgstr "" -#~ "LLM-Anbieter ist durch '{source}' " -#~ "gesperrt. Zum Ändern passen Sie " -#~ "llm_source in settings.yml an." +#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." +#~ msgstr "LLM-Anbieter ist durch '{source}' gesperrt. Zum Ändern passen Sie llm_source in settings.yml an." #~ msgid "Provider switched: {status}" #~ msgstr "Provider gewechselt: {status}" @@ -2557,13 +2572,6 @@ msgstr "" #~ msgid "Cache create" #~ msgstr "Cache-Erstellung" -#~ msgid "" -#~ "{count} skills - Space to toggle, " -#~ "Enter to save, / to search, t " -#~ "to sort, Esc to cancel" -#~ msgstr "" -#~ "{count} Skills - Leertaste zum " -#~ "Umschalten, Enter zum Speichern, / zum" -#~ " Suchen, t zum Sortieren, Esc zum " -#~ "Abbrechen" +#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" +#~ msgstr "{count} Skills - Leertaste zum Umschalten, Enter zum Speichern, / zum Suchen, t zum Sortieren, Esc zum Abbrechen" 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 08f4e29..f4f4928 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:45+0800\n" +"POT-Creation-Date: 2026-06-02 21:26+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: es\n" @@ -20,26 +20,18 @@ msgstr "" #: src/iac_code/config.py:164 #, python-brace-format msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}" -msgstr "" -"Valor de IAC_CODE_PROVIDER no válido: {!r}. Valores válidos (sin " -"distinguir mayúsculas/minúsculas): {}" +msgstr "Valor de IAC_CODE_PROVIDER no válido: {!r}. Valores válidos (sin distinguir mayúsculas/minúsculas): {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "" -"Unix domain socket transport is not supported on Windows. Use --transport" -" http or --transport stdio instead." +msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." msgstr "" -"El transporte de socket de dominio Unix no es compatible con Windows. Use" -" --transport http o --transport stdio en su lugar." +"El transporte de socket de dominio Unix no es compatible con Windows. Use --transport http o --transport stdio en su " +"lugar." #: src/iac_code/acp/slash_registry.py:44 #, python-brace-format -msgid "" -"Command '/{cmd_name}' is not supported over ACP. Supported commands: " -"{supported}" -msgstr "" -"El comando '/{cmd_name}' no está admitido en ACP. Comandos admitidos: " -"{supported}" +msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" +msgstr "El comando '/{cmd_name}' no está admitido en ACP. Comandos admitidos: {supported}" #: src/iac_code/acp/slash_registry.py:58 #, python-brace-format @@ -57,28 +49,21 @@ msgstr "Nada que compactar: la conversación está vacía." #: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "" -"Conversation too short to compact: all messages are within the recent " -"{turns}-turn preservation window." +msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." msgstr "" -"Conversación demasiado corta para compactar: todos los mensajes están " -"dentro de la ventana de retención de las últimas {turns} interacciones." +"Conversación demasiado corta para compactar: todos los mensajes están dentro de la ventana de retención de las " +"últimas {turns} interacciones." #: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "" -"Compaction failed. See logs for details.Compaction failed. See logs for " -"details.La compactación falló. Consulte los registros para obtener más " -"información." +"Compaction failed. See logs for details.Compaction failed. See logs for details.La compactación falló. Consulte los " +"registros para obtener más información." #: src/iac_code/acp/slash_registry.py:85 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent} reduction)." -" Context usage: {usage}" -msgstr "" -"Contexto compactado: {original} → {compacted} tokens (reducción del " -"{percent}). Uso del contexto: {usage}" +msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" +msgstr "Contexto compactado: {original} → {compacted} tokens (reducción del {percent}). Uso del contexto: {usage}" #: src/iac_code/acp/slash_registry.py:99 #, python-brace-format @@ -115,8 +100,8 @@ msgstr "Uso: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "El gestor de memoria no está disponible." -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "Permiso denegado." @@ -124,8 +109,8 @@ msgstr "Permiso denegado." #, python-brace-format msgid "Done ({tool_count} tool uses · {token_display} tokens)" msgstr "" -"Done ({tool_count} tool uses · {token_display} tokens)Completado " -"({tool_count} usos de herramientas · {token_display} tokens)" +"Done ({tool_count} tool uses · {token_display} tokens)Completado ({tool_count} usos de herramientas · {token_display}" +" tokens)" #: src/iac_code/agent/agent_tool.py:276 msgid "Explore" @@ -135,8 +120,7 @@ msgstr "Explorar" msgid "Plan" msgstr "Plan" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 -#: src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "Agente" @@ -188,16 +172,14 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-" -"code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " Solución: ejecute iac-code y escriba /auth\n" " o configure la variable de entorno: IAC_CODE_API_KEY=\n" -" Documentación: https://aliyun.github.io/iac-" -"code/es/docs/configuration/authentication\n" +" Documentación: https://aliyun.github.io/iac-code/es/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -219,12 +201,11 @@ msgstr "La instalación falló (PowerShell salió con código {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may " -"have been cancelled or the installer used a non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " +"non-standard path." msgstr "" -"El instalador terminó pero bash.exe no se encontró en las ubicaciones " -"habituales; UAC pudo haber sido cancelado o el instalador usó una ruta no" -" estándar." +"El instalador terminó pero bash.exe no se encontró en las ubicaciones habituales; UAC pudo haber sido cancelado o el " +"instalador usó una ruta no estándar." #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -247,14 +228,9 @@ msgstr "Instalar Git for Windows mediante el espejo npmmirror (solo Windows)." msgid "YAML config file containing A2A client options" msgstr "Archivo de configuración YAML con opciones del cliente A2A" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 -#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "" -"A2A client dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" -msgstr "" -"Faltan las dependencias del cliente A2A. Instálalas con: pip install " -"'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgstr "Faltan las dependencias del cliente A2A. Instálalas con: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 msgid "LLM model to use" @@ -272,8 +248,7 @@ msgstr "Formato de salida: text, json, stream-json" msgid "Maximum agent turns in headless mode" msgstr "Turnos máximos del agente en modo sin interfaz" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 -#: src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "Habilitar el registro de depuración" @@ -298,20 +273,12 @@ msgid "Install completion for the current shell." msgstr "Instalar la finalización automática para el shell actual." #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "" -"Show completion for the current shell, to copy it or customize the " -"installation." -msgstr "" -"Mostrar el script de finalización del shell actual para copiarlo o " -"personalizar la instalación." +msgid "Show completion for the current shell, to copy it or customize the installation." +msgstr "Mostrar el script de finalización del shell actual para copiarlo o personalizar la instalación." #: src/iac_code/cli/main.py:110 -msgid "" -"Comma-separated tool permission patterns to allow, e.g. 'bash(git " -"*),write_file'" -msgstr "" -"Patrones de permisos de herramientas a permitir (separados por comas), " -"p.ej. 'bash(git *),write_file'*),write_file'" +msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" +msgstr "Patrones de permisos de herramientas a permitir (separados por comas), p.ej. 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 msgid "Comma-separated tool permission patterns to deny" @@ -355,45 +322,29 @@ msgid "YAML config file for A2A server options" msgstr "Archivo de configuración YAML para opciones del servidor A2A" #: src/iac_code/cli/main.py:584 -msgid "" -"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " -"not a registered A2A port." +msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." msgstr "" -"Puerto del servidor HTTP. 41242 es el valor predeterminado de iac-code " -"inspirado en Gemini CLI, no un puerto A2A registrado." +"Puerto del servidor HTTP. 41242 es el valor predeterminado de iac-code inspirado en Gemini CLI, no un puerto A2A " +"registrado." #: src/iac_code/cli/main.py:589 -msgid "" -"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " -"redis-streams" -msgstr "" -"Transporte A2A: http, stdio, unix, websocket, grpc, grpc-jsonrpc o redis-" -"streams" +msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" +msgstr "Transporte A2A: http, stdio, unix, websocket, grpc, grpc-jsonrpc o redis-streams" #: src/iac_code/cli/main.py:595 -msgid "" -"Expose A2A thinking signal types; repeat for multiple. Values: raw-" -"thinking, tool-trace." -msgstr "" -"Expone tipos de señal de thinking A2A; repite para varios. Valores: raw-" -"thinking, tool-trace." +msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." +msgstr "Expone tipos de señal de thinking A2A; repite para varios. Valores: raw-thinking, tool-trace." #: src/iac_code/cli/main.py:646 -msgid "" -"A2A server dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" -msgstr "" -"Faltan las dependencias del servidor A2A. Instálalas con: pip install " -"'iac-code[a2a]'" +msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgstr "Faltan las dependencias del servidor A2A. Instálalas con: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "Envía un prompt a un endpoint JSON-RPC A2A." -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 -#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 -#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "URL del endpoint JSON-RPC A2A" @@ -418,48 +369,33 @@ msgstr "Metadatos del directorio de trabajo a enviar con la solicitud" msgid "A2A context ID to continue" msgstr "ID de contexto A2A a continuar" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 -#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 -#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 -#: src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "Token Bearer para solicitudes HTTP A2A" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 -#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 -#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 -#: src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "Nombre de usuario de autenticación básica para solicitudes HTTP A2A" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 -#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 -#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 -#: src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "Contraseña de autenticación básica para solicitudes HTTP A2A" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 -#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 -#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 -#: src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "Clave de API para solicitudes HTTP A2A" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 -#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 -#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 -#: src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "Nombre del encabezado HTTP para la clave de API A2A" @@ -495,10 +431,8 @@ msgstr "URL base del agente A2A" msgid "Get an A2A task." msgstr "Obtén una tarea A2A." -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 -#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 -#: src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "ID de tarea A2A" @@ -546,8 +480,7 @@ msgstr "Suscríbete a un flujo de eventos de tarea A2A." msgid "Create an A2A task push notification config." msgstr "Crea una configuración de notificación push de tarea A2A." -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 -#: src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "ID de configuración push" @@ -601,9 +534,7 @@ msgstr "ID de habilidad a resolver" #: src/iac_code/cli/main.py:1461 msgid "Prompt text used for tag/name route matching" -msgstr "" -"Texto del prompt utilizado para la coincidencia de rutas por " -"etiqueta/nombre" +msgstr "Texto del prompt utilizado para la coincidencia de rutas por etiqueta/nombre" #: src/iac_code/cli/main.py:1466 msgid "Directory for persisted A2A routes" @@ -673,227 +604,295 @@ msgstr "Gestionar habilidades" msgid "Show current session status" msgstr "Mostrar el estado actual de la sesión" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navegar" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:530 -#: src/iac_code/commands/auth.py:562 src/iac_code/commands/auth.py:569 -#: src/iac_code/commands/auth.py:604 src/iac_code/commands/auth.py:611 -#: src/iac_code/commands/auth.py:632 src/iac_code/commands/auth.py:1107 -#: src/iac_code/commands/auth.py:1319 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmar" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:528 -#: src/iac_code/commands/auth.py:530 src/iac_code/commands/auth.py:562 -#: src/iac_code/commands/auth.py:569 src/iac_code/commands/auth.py:604 -#: src/iac_code/commands/auth.py:611 src/iac_code/commands/auth.py:632 -#: src/iac_code/commands/auth.py:1107 src/iac_code/commands/auth.py:1217 -#: src/iac_code/commands/auth.py:1319 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "Atrás" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Keep" msgstr "Conservar" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Re-enter" msgstr "Volver a introducir" -#: src/iac_code/commands/auth.py:741 src/iac_code/commands/auth.py:853 -#: src/iac_code/commands/auth.py:911 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:946 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (actual)" -#: src/iac_code/commands/auth.py:744 +#: src/iac_code/commands/auth.py:752 msgid "Custom model..." msgstr "Modelo personalizado..." -#: src/iac_code/commands/auth.py:747 +#: src/iac_code/commands/auth.py:755 #, python-brace-format msgid "Select model for {provider}" msgstr "Seleccionar modelo para {provider}" -#: src/iac_code/commands/auth.py:749 +#: src/iac_code/commands/auth.py:757 msgid "Select model" msgstr "Seleccionar modelo" -#: src/iac_code/commands/auth.py:757 +#: src/iac_code/commands/auth.py:765 msgid "Enter custom model name: " msgstr "Introduzca el nombre del modelo personalizado: " -#: src/iac_code/commands/auth.py:783 +#: src/iac_code/commands/auth.py:791 msgid "Error: console not available" msgstr "Error: la consola no está disponible" -#: src/iac_code/commands/auth.py:810 +#: src/iac_code/commands/auth.py:818 msgid "Configure LLM Provider" msgstr "Configurar proveedor LLM" -#: src/iac_code/commands/auth.py:811 +#: src/iac_code/commands/auth.py:819 msgid "Configure IaC Cloud Service" msgstr "Configurar servicio cloud IaC" -#: src/iac_code/commands/auth.py:813 +#: src/iac_code/commands/auth.py:821 msgid "Select configuration type" msgstr "Seleccionar tipo de configuración" -#: src/iac_code/commands/auth.py:815 src/iac_code/commands/auth.py:971 -#: src/iac_code/commands/auth.py:987 src/iac_code/commands/auth.py:1066 -#: src/iac_code/commands/auth.py:1258 src/iac_code/commands/auth.py:1299 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 +#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "Autenticación cancelada" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:951 -#: src/iac_code/commands/auth.py:1041 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "Seleccionar proveedor — {group}" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:909 -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "Terceros" -#: src/iac_code/commands/auth.py:867 +#: src/iac_code/commands/auth.py:875 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:868 src/iac_code/commands/auth.py:1019 +#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 msgid "Configured" msgstr "Configurado" -#: src/iac_code/commands/auth.py:923 +#: src/iac_code/commands/auth.py:931 msgid "Select provider" msgstr "Seleccionar proveedor" -#: src/iac_code/commands/auth.py:964 +#: src/iac_code/commands/auth.py:972 #, python-brace-format msgid "Configure {provider}" msgstr "Configurar {provider}" -#: src/iac_code/commands/auth.py:980 +#: src/iac_code/commands/auth.py:988 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Introduzca la API key para {provider}" -#: src/iac_code/commands/auth.py:1018 +#: src/iac_code/commands/auth.py:1026 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:1027 src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1028 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1029 +#: src/iac_code/commands/auth.py:1037 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1030 +#: src/iac_code/commands/auth.py:1038 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1031 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1032 +#: src/iac_code/commands/auth.py:1040 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1033 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1034 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1035 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1037 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1039 +#: src/iac_code/commands/auth.py:1047 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1048 msgid "Compatible" msgstr "Compatible" -#: src/iac_code/commands/auth.py:1057 +#: src/iac_code/commands/auth.py:1065 msgid "Select Cloud Provider" msgstr "Seleccionar proveedor de cloud" -#: src/iac_code/commands/auth.py:1073 +#: src/iac_code/commands/auth.py:1081 msgid "Credential" msgstr "Credencial" -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 -#: src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 +#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Región" -#: src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1084 msgid "Configure Alibaba Cloud" msgstr "Configurar Alibaba Cloud" -#: src/iac_code/commands/auth.py:1178 +#: src/iac_code/commands/auth.py:1186 msgid "Current configuration" msgstr "Configuración actual" -#: src/iac_code/commands/auth.py:1180 +#: src/iac_code/commands/auth.py:1188 msgid "Mode" msgstr "Modo" -#: src/iac_code/commands/auth.py:1187 +#: src/iac_code/commands/auth.py:1194 msgid "(not set)" msgstr "(sin definir)" -#: src/iac_code/commands/auth.py:1204 +#: src/iac_code/commands/auth.py:1203 +msgid "AccessKey" +msgstr "AccessKey" + +#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +msgid "STS Token" +msgstr "Token STS" + +#: src/iac_code/commands/auth.py:1207 +msgid "RAM Role" +msgstr "Rol RAM" + +#: src/iac_code/commands/auth.py:1209 +msgid "OAuth Login (Browser)" +msgstr "Inicio de sesión OAuth (navegador)" + +#: src/iac_code/commands/auth.py:1215 +msgid "AccessKey ID" +msgstr "ID de AccessKey" + +#: src/iac_code/commands/auth.py:1216 +msgid "AccessKey Secret" +msgstr "Secreto de AccessKey" + +#: src/iac_code/commands/auth.py:1218 +msgid "RAM Role ARN" +msgstr "ARN del rol RAM" + +#: src/iac_code/commands/auth.py:1219 +msgid "Session Name" +msgstr "Nombre de sesión" + +#: src/iac_code/commands/auth.py:1220 +msgid "OAuth Site Type" +msgstr "Tipo de sitio OAuth" + +#: src/iac_code/commands/auth.py:1221 +msgid "OAuth Access Token" +msgstr "Token de acceso OAuth" + +#: src/iac_code/commands/auth.py:1222 +msgid "OAuth Refresh Token" +msgstr "Token de actualización OAuth" + +#: src/iac_code/commands/auth.py:1223 +msgid "OAuth Access Token Expire" +msgstr "Expiración del token de acceso OAuth" + +#: src/iac_code/commands/auth.py:1224 +msgid "OAuth Refresh Token Expire" +msgstr "Expiración del token de actualización OAuth" + +#: src/iac_code/commands/auth.py:1225 +msgid "STS Expiration" +msgstr "Expiración STS" + +#: src/iac_code/commands/auth.py:1382 +msgid "China" +msgstr "China" + +#: src/iac_code/commands/auth.py:1383 +msgid "International" +msgstr "Internacional" + +#: src/iac_code/commands/auth.py:1385 +msgid "Choose site type" +msgstr "Elegir tipo de sitio" + +#: src/iac_code/commands/auth.py:1400 +#, python-brace-format +msgid "Alibaba Cloud OAuth login failed: {error}" +msgstr "Error de inicio de sesión OAuth de Alibaba Cloud: {error}" + +#: src/iac_code/commands/auth.py:1416 +msgid "Configured: Alibaba Cloud OAuth credentials saved" +msgstr "Configurado: credenciales OAuth de Alibaba Cloud guardadas" + +#: src/iac_code/commands/auth.py:1428 msgid "Configure Alibaba Cloud credentials" msgstr "Configurar credenciales de Alibaba Cloud" -#: src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1441 msgid "Reconfigure credential" msgstr "Reconfigurar la credencial" -#: src/iac_code/commands/auth.py:1230 +#: src/iac_code/commands/auth.py:1454 msgid "Select credential type" msgstr "Seleccionar tipo de credencial" -#: src/iac_code/commands/auth.py:1280 +#: src/iac_code/commands/auth.py:1510 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:1287 +#: src/iac_code/commands/auth.py:1517 msgid "Configure Alibaba Cloud region" msgstr "Configurar la región de Alibaba Cloud" -#: src/iac_code/commands/auth.py:1313 +#: src/iac_code/commands/auth.py:1543 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Configurado: región de Alibaba Cloud guardada en ~/.iac-code" @@ -911,12 +910,8 @@ msgstr "No hay ningún bucle de agente activo." #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent_display} " -"reduction). Context usage: {usage_display}" -msgstr "" -"Contexto compactado: {original} → {compacted} tokens (reducción " -"{percent_display}). Uso del contexto: {usage_display}" +msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" +msgstr "Contexto compactado: {original} → {compacted} tokens (reducción {percent_display}). Uso del contexto: {usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -926,8 +921,7 @@ msgstr "El comando debug requiere un contexto." msgid "No active session." msgstr "No hay ninguna sesión activa." -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 -#: src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "No hay proveedores configurados. Ejecute /auth primero." @@ -1021,12 +1015,8 @@ msgstr "Memoria '{name}' eliminada." #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "" -"Model is managed by '{source}'. To change model, modify it in {source} or" -" switch provider via /auth." -msgstr "" -"El modelo es gestionado por '{source}'. Para cambiarlo, modifíquelo en " -"{source} o cambie de proveedor mediante /auth." +msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." +msgstr "El modelo es gestionado por '{source}'. Para cambiarlo, modifíquelo en {source} o cambie de proveedor mediante /auth." #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1050,8 +1040,8 @@ msgstr "Reanudar solo está disponible en modo interactivo." #: src/iac_code/commands/resume.py:27 msgid "Resume is unavailable: session index not initialised." msgstr "" -"Resume is unavailable: session index not initialised.Reanudar no está " -"disponible: el índice de sesiones no se ha inicializado." +"Resume is unavailable: session index not initialised.Reanudar no está disponible: el índice de sesiones no se ha " +"inicializado." #: src/iac_code/commands/resume.py:32 #, python-brace-format @@ -1094,8 +1084,7 @@ msgstr "Sesión" msgid "Provider" msgstr "Proveedor" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 -#: src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 msgid "not configured" msgstr "no configurado" @@ -1196,10 +1185,8 @@ msgstr "¡Operación cancelada!" #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." msgstr "" -"Cannot determine provider for model: {model}. Run /auth to " -"configure.Cannot determine provider for model: {model}. Run /auth to " -"configure.No se puede determinar el proveedor para el modelo: {model}. " -"Ejecute /auth para configurar." +"Cannot determine provider for model: {model}. Run /auth to configure.Cannot determine provider for model: {model}. " +"Run /auth to configure.No se puede determinar el proveedor para el modelo: {model}. Ejecute /auth para configurar." #: src/iac_code/providers/manager.py:95 #, python-brace-format @@ -1208,34 +1195,26 @@ msgstr "Clave de proveedor desconocida: '{key}'. Ejecute /auth para configurar." #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "" -"No API key configured for provider '{provider}' (model: {model}). Run " -"/auth to configure." -msgstr "" -"No se ha configurado una clave API para el proveedor '{provider}' " -"(modelo: {model}). Ejecute /auth para configurar." +msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." +msgstr "No se ha configurado una clave API para el proveedor '{provider}' (modelo: {model}). Ejecute /auth para configurar." #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct " -"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " -"suffix (e.g. {base_url}/v1)." +"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " +"endpoints require a /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"La API no devolvió datos. Compruebe que la API Base URL es correcta " -"(actual: {base_url}). Muchos endpoints compatibles con OpenAI requieren " -"el sufijo /v1 (p. ej., {base_url}/v1)." +"La API no devolvió datos. Compruebe que la API Base URL es correcta (actual: {base_url}). Muchos endpoints " +"compatibles con OpenAI requieren el sufijo /v1 (p. ej., {base_url}/v1)." #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is " -"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" -" /v1 suffix (e.g. {base_url}/v1)." +"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" +"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"La API devolvió una respuesta no válida. Compruebe que la API Base URL es" -" correcta (actual: {base_url}). Muchos endpoints compatibles con OpenAI " -"requieren el sufijo /v1 (p. ej., {base_url}/v1)." +"La API devolvió una respuesta no válida. Compruebe que la API Base URL es correcta (actual: {base_url}). Muchos " +"endpoints compatibles con OpenAI requieren el sufijo /v1 (p. ej., {base_url}/v1)." #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1316,35 +1295,131 @@ msgstr "Compatible con Anthropic" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " -"support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " -"mode (remove 'llm_source: qwenpaw' from settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " +"settings.yml)." msgstr "" -"[Modo QwenPaw] Proveedor desconocido '{provider_id}'. iac-code no soporta" -" este proveedor.\n" +"[Modo QwenPaw] Proveedor desconocido '{provider_id}'. iac-code no soporta este proveedor.\n" "IDs de proveedores QwenPaw soportados: {supported_ids}\n" -"Solución: cambie a un proveedor soportado en QwenPaw, o desactive el modo" -" QwenPaw (elimine 'llm_source: qwenpaw' de settings.yml)." +"Solución: cambie a un proveedor soportado en QwenPaw, o desactive el modo QwenPaw (elimine 'llm_source: qwenpaw' de " +"settings.yml)." #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "Modo --permission-mode no válido: {!r}. Valores válidos: {}" -#: src/iac_code/services/permissions/pipeline.py:54 -#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "¿Permitir {}?" +#: src/iac_code/services/providers/aliyun.py:144 +msgid "Alibaba Cloud OAuth site is missing." +msgstr "Falta el sitio OAuth de Alibaba Cloud." + +#: src/iac_code/services/providers/aliyun.py:150 +msgid "Alibaba Cloud OAuth refresh token is missing." +msgstr "Falta el token de actualización OAuth de Alibaba Cloud." + +#: src/iac_code/services/providers/aliyun.py:158 +msgid "Alibaba Cloud OAuth access token is missing." +msgstr "Falta el token de acceso OAuth de Alibaba Cloud." + +#: src/iac_code/services/providers/aliyun_oauth.py:83 +msgid "Run /auth and choose OAuth Login (Browser)." +msgstr "Ejecute /auth y elija Inicio de sesión OAuth (navegador)." + +#: src/iac_code/services/providers/aliyun_oauth.py:106 +#, python-brace-format +msgid "Unknown Aliyun OAuth site: {site_type}" +msgstr "Sitio OAuth de Aliyun desconocido: {site_type}" + +#: src/iac_code/services/providers/aliyun_oauth.py:164 +msgid "Not found" +msgstr "No encontrado" + +#: src/iac_code/services/providers/aliyun_oauth.py:170 +msgid "invalid state" +msgstr "estado inválido" + +#: src/iac_code/services/providers/aliyun_oauth.py:171 +msgid "Invalid state" +msgstr "Estado inválido" + +#: src/iac_code/services/providers/aliyun_oauth.py:176 +msgid "code not found" +msgstr "código de autorización no encontrado" + +#: src/iac_code/services/providers/aliyun_oauth.py:177 +msgid "Authorization code not found" +msgstr "Código de autorización no encontrado" + +#: src/iac_code/services/providers/aliyun_oauth.py:181 +msgid "Authorization successful. You can close this window." +msgstr "Autorización correcta. Puede cerrar esta ventana." + +#: src/iac_code/services/providers/aliyun_oauth.py:212 +#, python-brace-format +msgid "No available callback port in range {start}-{end}" +msgstr "No hay ningún puerto de callback disponible en el intervalo {start}-{end}" + +#: src/iac_code/services/providers/aliyun_oauth.py:227 +msgid "OAuth login cancelled." +msgstr "Inicio de sesión OAuth cancelado." + +#: src/iac_code/services/providers/aliyun_oauth.py:278 +msgid "Open in your browser:" +msgstr "Abrir en el navegador:" + +#: src/iac_code/services/providers/aliyun_oauth.py:299 +msgid "Waiting for browser authorization" +msgstr "Esperando la autorización del navegador" + +#: src/iac_code/services/providers/aliyun_oauth.py:300 +msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." +msgstr "1. El navegador puede mostrar official-cli; es la aplicación OAuth oficial de la CLI de Alibaba Cloud." + +#: src/iac_code/services/providers/aliyun_oauth.py:302 +msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgstr "" +"2. Si se requiere asignación, asigne el usuario RAM o el rol RAM que tiene la sesión iniciada. Los grupos de usuarios" +" no son compatibles." + +#: src/iac_code/services/providers/aliyun_oauth.py:306 +msgid "" +"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " +"out of Alibaba Cloud and sign in again." +msgstr "" +"3. Después de la asignación, cierre la página de autorización antigua y ejecute de nuevo Inicio de sesión OAuth " +"(navegador). Si sigue fallando, cierre sesión en Alibaba Cloud e iníciela de nuevo." + +#: src/iac_code/services/providers/aliyun_oauth.py:310 +msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgstr "" +"4. Las credenciales STS se actualizan cuando es posible hasta que Alibaba Cloud las caduque. Si la actualización " +"falla, ejecute /auth de nuevo." + +#: src/iac_code/services/providers/aliyun_oauth.py:313 +msgid "Press Esc to cancel while waiting." +msgstr "Pulse Esc para cancelar la espera." + +#: src/iac_code/services/providers/aliyun_oauth.py:321 +msgid "" +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" +" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " +"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +msgstr "" +"Se agotó el tiempo esperando el callback OAuth. Si Alibaba Cloud le pidió asignar la aplicación official-cli, " +"asígnela al usuario RAM o al rol RAM exacto que tiene la sesión iniciada. Los grupos de usuarios no son compatibles. " +"Después cierre la página de autorización antigua, cierre la sesión de Alibaba Cloud e iníciela de nuevo si es " +"necesario, y ejecute /auth para elegir de nuevo Inicio de sesión OAuth (navegador)." + #: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." -msgstr "" -"La habilidad '{name}' está deshabilitada. Ejecute /skills para " -"habilitarla." +msgstr "La habilidad '{name}' está deshabilitada. Ejecute /skills para habilitarla." #: src/iac_code/skills/skill_tool.py:137 #, python-brace-format @@ -1361,12 +1436,10 @@ msgid "Skill disabled: {name}" msgstr "Habilidad deshabilitada: {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "" -"Review changed code for reuse, quality, and efficiency, then fix issues " -"found." +msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." msgstr "" -"Revise el código modificado en cuanto a reutilización, calidad y " -"eficiencia; a continuación, corrija los problemas detectados." +"Revise el código modificado en cuanto a reutilización, calidad y eficiencia; a continuación, corrija los problemas " +"detectados." #: src/iac_code/tools/edit_file.py:116 msgid "Edit" @@ -1464,9 +1537,8 @@ msgstr "La URL no puede estar vacía." #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Invalid" -" URL: missing scheme (e.g. http:// or https://). Got: {url}URL no válida:" -" falta el esquema (p. ej. http:// o https://). Recibido: {url}" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Invalid URL: missing scheme (e.g. http:// or " +"https://). Got: {url}URL no válida: falta el esquema (p. ej. http:// o https://). Recibido: {url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1575,8 +1647,7 @@ msgstr "Regla(s) de denegación coincidente(s): {}" msgid "matched ask rule(s): {}" msgstr "Regla(s) de consulta coincidente(s): {}" -#: src/iac_code/tools/bash/permissions.py:154 -#: src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "Regla(s) de permiso coincidente(s): {}" @@ -1633,8 +1704,7 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "Llamando a {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 -#: src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Llamada correcta" @@ -1647,8 +1717,7 @@ msgstr "Respuesta recibida ({count} líneas)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 -#: src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "Ejecutando {action}..." @@ -1793,11 +1862,11 @@ msgstr "Importación completada" msgid "IMPORT_FAILED" msgstr "Importación fallida" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:171 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:172 msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "Llamada correcta (RequestId: {request_id})" @@ -1854,9 +1923,8 @@ msgstr "Se encontraron {count} documentos (total {total})" #: src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py:143 msgid "Use web_fetch tool to read full document content if needed." msgstr "" -"Use web_fetch tool to read full document content if needed.Use web_fetch " -"tool to read full document content if needed.Si es necesario, utilice la " -"herramienta web_fetch para leer el documento completo." +"Use web_fetch tool to read full document content if needed.Use web_fetch tool to read full document content if " +"needed.Si es necesario, utilice la herramienta web_fetch para leer el documento completo." #: src/iac_code/tools/cloud/aliyun/ros_stack.py:143 msgid "ROS Stack" @@ -1878,40 +1946,27 @@ msgid "" "Context:\n" "{context}" msgstr "" -"Error de sintaxis YAML en la plantilla (línea {line}, columna {col}): " -"{problem}\n" +"Error de sintaxis YAML en la plantilla (línea {line}, columna {col}): {problem}\n" "Contexto:\n" "{context}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:66 #, python-brace-format msgid "Template JSON syntax error (line {line}, column {col}): {msg}" -msgstr "" -"Error de sintaxis JSON en la plantilla (línea {line}, columna {col}): " -"{msg}" +msgstr "Error de sintaxis JSON en la plantilla (línea {line}, columna {col}): {msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "" -"Template {fmt} parse result is not an object (dict), please check the " -"template format" -msgstr "" -"El resultado del análisis {fmt} de la plantilla no es un objeto (dict), " -"por favor verifique el formato de la plantilla" +msgid "Template {fmt} parse result is not an object (dict), please check the template format" +msgstr "El resultado del análisis {fmt} de la plantilla no es un objeto (dict), por favor verifique el formato de la plantilla" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "" -"Template is missing ROSTemplateFormatVersion (ROS templates must include " -"this field, e.g. '2015-09-01')" -msgstr "" -"Falta ROSTemplateFormatVersion en la plantilla (las plantillas ROS deben " -"incluir este campo, ej. '2015-09-01')" +msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" +msgstr "Falta ROSTemplateFormatVersion en la plantilla (las plantillas ROS deben incluir este campo, ej. '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" -msgstr "" -"Falta Resources en la plantilla (las plantillas ROS deben incluir " -"Resources)" +msgstr "Falta Resources en la plantilla (las plantillas ROS deben incluir Resources)" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:112 #, python-brace-format @@ -1920,12 +1975,8 @@ msgstr "Resources debe ser un objeto (dict), el tipo actual es {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "" -"Resource '{name}' definition must be an object (dict), current type is " -"{type}" -msgstr "" -"La definición del recurso '{name}' debe ser un objeto (dict), el tipo " -"actual es {type}" +msgid "Resource '{name}' definition must be an object (dict), current type is {type}" +msgstr "La definición del recurso '{name}' debe ser un objeto (dict), el tipo actual es {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 #, python-brace-format @@ -1935,17 +1986,11 @@ msgstr "Al recurso '{name}' le falta el campo Type" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:129 #, python-brace-format msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" -msgstr "" -"El recurso '{name}' tiene el tipo incorrecto '{wrong}', debería ser " -"'{correct}'" +msgstr "El recurso '{name}' tiene el tipo incorrecto '{wrong}', debería ser '{correct}'" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "" -"Template structure validation found the following issues, please fix and " -"retry:" -msgstr "" -"La validación de la estructura de la plantilla encontró los siguientes " -"problemas, por favor corrija y reintente:" +msgid "Template structure validation found the following issues, please fix and retry:" +msgstr "La validación de la estructura de la plantilla encontró los siguientes problemas, por favor corrija y reintente:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 #, python-brace-format @@ -1981,14 +2026,12 @@ msgstr "Modo depuración" msgid "Log file" msgstr "Archivo de registro" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 -#: src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "Razonamiento durante {seconds:.1f} s" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 -#: src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o para expandir)" @@ -2096,9 +2139,7 @@ msgstr "Actualizar ahora" #: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." -msgstr "" -"Ejecuta el comando de actualización mostrado y sale cuando finalice " -"correctamente." +msgstr "Ejecuta el comando de actualización mostrado y sale cuando finalice correctamente." #: src/iac_code/ui/repl.py:499 msgid "Skip" @@ -2114,9 +2155,7 @@ msgstr "Omitir hasta la siguiente versión" #: src/iac_code/ui/repl.py:506 msgid "Hide this update until a newer version is available." -msgstr "" -"Ocultar esta actualización hasta que haya una versión más nueva " -"disponible." +msgstr "Ocultar esta actualización hasta que haya una versión más nueva disponible." #: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." @@ -2141,17 +2180,14 @@ msgstr "La compatibilidad con comandos de shell no está disponible." #: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." -msgstr "" -"Habilidad desconocida: ${name}. Escribe / para listar comandos y " -"habilidades." +msgstr "Habilidad desconocida: ${name}. Escribe / para listar comandos y habilidades." #: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" -"Unknown command: /{name}. Type /help for available commands.Unknown " -"command: /{name}. Type /help for available commands.Comando desconocido: " -"/{name}. Escriba /help para ver los comandos disponibles." +"Unknown command: /{name}. Type /help for available commands.Unknown command: /{name}. Type /help for available " +"commands.Comando desconocido: /{name}. Escriba /help para ver los comandos disponibles." #: src/iac_code/ui/repl.py:833 #, python-brace-format @@ -2198,12 +2234,8 @@ msgstr "(Comando copiado al portapapeles)" #: src/iac_code/ui/repl.py:1451 #, python-brace-format -msgid "" -"Current model {model} does not support image input. Use /model to switch " -"to a vision-capable model." -msgstr "" -"El modelo actual {model} no admite entrada de imágenes. Usa /model para " -"cambiar a un modelo con capacidad de visión." +msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." +msgstr "El modelo actual {model} no admite entrada de imágenes. Usa /model para cambiar a un modelo con capacidad de visión." #: src/iac_code/ui/repl.py:1460 #, python-brace-format @@ -2211,12 +2243,8 @@ msgid "Image error: {err}" msgstr "Error de imagen: {err}" #: src/iac_code/ui/repl.py:1477 -msgid "" -"Failed to persist image to cache; it will only exist in memory for this " -"turn." -msgstr "" -"No se pudo persistir la imagen en la caché; solo existirá en memoria " -"durante este turno." +msgid "Failed to persist image to cache; it will only exist in memory for this turn." +msgstr "No se pudo persistir la imagen en la caché; solo existirá en memoria durante este turno." #: src/iac_code/ui/spinner.py:52 msgid "Processing" @@ -2406,12 +2434,8 @@ msgstr "{current} de {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "" -"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " -"cancel" -msgstr "" -"{count} habilidades - Espacio para alternar, Enter para guardar, Tab para" -" ordenar, Esc para cancelar" +msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" +msgstr "{count} habilidades - Espacio para alternar, Enter para guardar, Tab para ordenar, Esc para cancelar" #: src/iac_code/ui/dialogs/skills_picker.py:171 #, python-brace-format @@ -2497,12 +2521,8 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code en Windows requiere Git for Windows." #: src/iac_code/utils/platform.py:41 -msgid "" -"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " -"variable." -msgstr "" -"Si está instalado pero no está en el PATH, establezca la variable de " -"entorno IAC_CODE_GIT_BASH_PATH." +msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." +msgstr "Si está instalado pero no está en el PATH, establezca la variable de entorno IAC_CODE_GIT_BASH_PATH." #: src/iac_code/utils/platform.py:44 msgid "To install:" @@ -2513,12 +2533,8 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " Opción 1 - winget (requiere acceso a github.com):" #: src/iac_code/utils/platform.py:52 -msgid "" -" Option 2 - if you cannot reach github.com, run this to install via " -"npmmirror:" -msgstr "" -" Opción 2 - si no puedes acceder a github.com, ejecuta esto para " -"instalar vía npmmirror:" +msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" +msgstr " Opción 2 - si no puedes acceder a github.com, ejecuta esto para instalar vía npmmirror:" #~ msgid "toggle preview" #~ msgstr "alternar vista previa" @@ -2541,14 +2557,8 @@ msgstr "" #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "" -#~ "LLM provider is locked by '{source}'." -#~ " To change, modify llm_source in " -#~ "settings.yml." -#~ msgstr "" -#~ "El proveedor LLM está bloqueado por " -#~ "'{source}'. Para cambiar, modifique llm_source" -#~ " en settings.yml." +#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." +#~ msgstr "El proveedor LLM está bloqueado por '{source}'. Para cambiar, modifique llm_source en settings.yml." #~ msgid "Provider switched: {status}" #~ msgstr "Provider cambiado: {status}" @@ -2565,13 +2575,6 @@ msgstr "" #~ msgid "Cache create" #~ msgstr "Creación de caché" -#~ msgid "" -#~ "{count} skills - Space to toggle, " -#~ "Enter to save, / to search, t " -#~ "to sort, Esc to cancel" -#~ msgstr "" -#~ "{count} habilidades - Espacio para " -#~ "alternar, Enter para guardar, / para " -#~ "buscar, t para ordenar, Esc para " -#~ "cancelar" +#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" +#~ msgstr "{count} habilidades - Espacio para alternar, Enter para guardar, / para buscar, t para ordenar, Esc para cancelar" 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 bf77cbd..2ce3c04 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:45+0800\n" +"POT-Creation-Date: 2026-06-02 21:26+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: fr\n" @@ -20,26 +20,18 @@ msgstr "" #: src/iac_code/config.py:164 #, python-brace-format msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}" -msgstr "" -"Valeur IAC_CODE_PROVIDER invalide : {!r}. Valeurs valides (insensible à " -"la casse) : {}" +msgstr "Valeur IAC_CODE_PROVIDER invalide : {!r}. Valeurs valides (insensible à la casse) : {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "" -"Unix domain socket transport is not supported on Windows. Use --transport" -" http or --transport stdio instead." +msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." msgstr "" -"Le transport par socket de domaine Unix n'est pas pris en charge sous " -"Windows. Utilisez --transport http ou --transport stdio à la place." +"Le transport par socket de domaine Unix n'est pas pris en charge sous Windows. Utilisez --transport http ou " +"--transport stdio à la place." #: src/iac_code/acp/slash_registry.py:44 #, python-brace-format -msgid "" -"Command '/{cmd_name}' is not supported over ACP. Supported commands: " -"{supported}" -msgstr "" -"La commande « /{cmd_name} » n’est pas prise en charge via ACP. Commandes " -"prises en charge : {supported}" +msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" +msgstr "La commande « /{cmd_name} » n’est pas prise en charge via ACP. Commandes prises en charge : {supported}" #: src/iac_code/acp/slash_registry.py:58 #, python-brace-format @@ -57,12 +49,10 @@ msgstr "Rien à compacter : la conversation est vide." #: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "" -"Conversation too short to compact: all messages are within the recent " -"{turns}-turn preservation window." +msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." msgstr "" -"Conversation trop courte pour être compactée : tous les messages se " -"situent dans la fenêtre de conservation des {turns} derniers tours." +"Conversation trop courte pour être compactée : tous les messages se situent dans la fenêtre de conservation des " +"{turns} derniers tours." #: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." @@ -70,12 +60,8 @@ msgstr "Échec de la compaction. Consultez les journaux pour plus de détails." #: src/iac_code/acp/slash_registry.py:85 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent} reduction)." -" Context usage: {usage}" -msgstr "" -"Contexte compacté : {original} → {compacted} tokens (réduction " -"{percent}). Utilisation du contexte : {usage}" +msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" +msgstr "Contexte compacté : {original} → {compacted} tokens (réduction {percent}). Utilisation du contexte : {usage}" #: src/iac_code/acp/slash_registry.py:99 #, python-brace-format @@ -112,8 +98,8 @@ msgstr "Utilisation : /debug [on|off]" msgid "Memory manager is unavailable." msgstr "Le gestionnaire de mémoire est indisponible." -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "Permission refusée." @@ -130,8 +116,7 @@ msgstr "Explorer" msgid "Plan" msgstr "Plan" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 -#: src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "Agent" @@ -183,17 +168,14 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-" -"code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " Correction : exécutez iac-code puis tapez /auth\n" -" ou définissez la variable d’environnement : IAC_CODE_API_KEY=\n" -" Documentation : https://aliyun.github.io/iac-" -"code/fr/docs/configuration/authentication\n" +" ou définissez la variable d’environnement : IAC_CODE_API_KEY=\n" +" Documentation : https://aliyun.github.io/iac-code/fr/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -206,9 +188,7 @@ msgstr "Installation de Git for Windows via npmmirror..." #: src/iac_code/cli/install_git_bash.py:59 msgid "powershell.exe was not found on PATH; cannot run installer." -msgstr "" -"powershell.exe est introuvable dans PATH ; impossible d'exécuter " -"l'installateur." +msgstr "powershell.exe est introuvable dans PATH ; impossible d'exécuter l'installateur." #: src/iac_code/cli/install_git_bash.py:66 #, python-brace-format @@ -217,12 +197,11 @@ msgstr "L'installation a échoué (PowerShell est sorti avec le code {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may " -"have been cancelled or the installer used a non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " +"non-standard path." msgstr "" -"L'installateur s'est terminé mais bash.exe est introuvable dans les " -"emplacements habituels ; UAC a peut-être été annulé ou l'installateur a " -"utilisé un chemin non standard." +"L'installateur s'est terminé mais bash.exe est introuvable dans les emplacements habituels ; UAC a peut-être été " +"annulé ou l'installateur a utilisé un chemin non standard." #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -245,14 +224,9 @@ msgstr "Installer Git for Windows via le miroir npmmirror (Windows uniquement)." msgid "YAML config file containing A2A client options" msgstr "Fichier de configuration YAML contenant les options client A2A" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 -#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "" -"A2A client dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" -msgstr "" -"Les dépendances du client A2A sont manquantes. Installez-les avec : pip " -"install 'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgstr "Les dépendances du client A2A sont manquantes. Installez-les avec : pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 msgid "LLM model to use" @@ -270,8 +244,7 @@ msgstr "Format de sortie : text, json, stream-json" msgid "Maximum agent turns in headless mode" msgstr "Nombre maximal de tours d’agent en mode headless" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 -#: src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "Activer la journalisation debug" @@ -296,20 +269,12 @@ msgid "Install completion for the current shell." msgstr "Installer la complétion pour le shell actuel." #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "" -"Show completion for the current shell, to copy it or customize the " -"installation." -msgstr "" -"Afficher la complétion pour le shell actuel afin de la copier ou de " -"personnaliser l’installation." +msgid "Show completion for the current shell, to copy it or customize the installation." +msgstr "Afficher la complétion pour le shell actuel afin de la copier ou de personnaliser l’installation." #: src/iac_code/cli/main.py:110 -msgid "" -"Comma-separated tool permission patterns to allow, e.g. 'bash(git " -"*),write_file'" -msgstr "" -"Modèles de permissions d'outils à autoriser (séparés par des virgules), " -"ex. 'bash(git *),write_file'*),write_file'" +msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" +msgstr "Modèles de permissions d'outils à autoriser (séparés par des virgules), ex. 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 msgid "Comma-separated tool permission patterns to deny" @@ -318,8 +283,8 @@ msgstr "Modèles de permissions d'outils à refuser (séparés par des virgules) #: src/iac_code/cli/main.py:120 msgid "Permission mode: default, accept_edits, bypass_permissions, dont_ask" msgstr "" -"Permission mode: default, accept_edits, bypass_permissions, dont_askMode " -"de permissions : default, accept_edits, bypass_permissions, dont_ask" +"Permission mode: default, accept_edits, bypass_permissions, dont_askMode de permissions : default, accept_edits, " +"bypass_permissions, dont_ask" #: src/iac_code/cli/main.py:151 msgid "Error: --resume and --continue cannot be used together." @@ -355,45 +320,27 @@ msgid "YAML config file for A2A server options" msgstr "Fichier de configuration YAML pour les options du serveur A2A" #: src/iac_code/cli/main.py:584 -msgid "" -"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " -"not a registered A2A port." -msgstr "" -"Port du serveur HTTP. 41242 est la valeur par défaut d'iac-code inspirée " -"de Gemini CLI, pas un port A2A enregistré." +msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." +msgstr "Port du serveur HTTP. 41242 est la valeur par défaut d'iac-code inspirée de Gemini CLI, pas un port A2A enregistré." #: src/iac_code/cli/main.py:589 -msgid "" -"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " -"redis-streams" -msgstr "" -"Transport A2A : http, stdio, unix, websocket, grpc, grpc-jsonrpc ou " -"redis-streams" +msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" +msgstr "Transport A2A : http, stdio, unix, websocket, grpc, grpc-jsonrpc ou redis-streams" #: src/iac_code/cli/main.py:595 -msgid "" -"Expose A2A thinking signal types; repeat for multiple. Values: raw-" -"thinking, tool-trace." -msgstr "" -"Expose les types de signal de thinking A2A ; répétez pour en fournir " -"plusieurs. Valeurs : raw-thinking, tool-trace." +msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." +msgstr "Expose les types de signal de thinking A2A ; répétez pour en fournir plusieurs. Valeurs : raw-thinking, tool-trace." #: src/iac_code/cli/main.py:646 -msgid "" -"A2A server dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" -msgstr "" -"Les dépendances du serveur A2A sont manquantes. Installez-les avec : pip " -"install 'iac-code[a2a]'" +msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgstr "Les dépendances du serveur A2A sont manquantes. Installez-les avec : pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "Envoie un prompt à un point de terminaison JSON-RPC A2A." -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 -#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 -#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "URL du point de terminaison JSON-RPC A2A" @@ -418,48 +365,33 @@ msgstr "Métadonnées du répertoire de travail à envoyer avec la requête" msgid "A2A context ID to continue" msgstr "ID de contexte A2A à poursuivre" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 -#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 -#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 -#: src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "Jeton Bearer pour les requêtes HTTP A2A" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 -#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 -#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 -#: src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "Nom d'utilisateur d'authentification basique pour les requêtes HTTP A2A" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 -#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 -#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 -#: src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "Mot de passe d'authentification basique pour les requêtes HTTP A2A" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 -#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 -#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 -#: src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "Clé d'API pour les requêtes HTTP A2A" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 -#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 -#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 -#: src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "Nom de l'en-tête HTTP pour la clé d'API A2A" @@ -495,10 +427,8 @@ msgstr "URL de base de l'agent A2A" msgid "Get an A2A task." msgstr "Récupère une tâche A2A." -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 -#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 -#: src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "ID de tâche A2A" @@ -546,8 +476,7 @@ msgstr "S'abonne à un flux d'événements de tâche A2A." msgid "Create an A2A task push notification config." msgstr "Crée une configuration de notifications push de tâche A2A." -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 -#: src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "ID de configuration push" @@ -671,235 +600,301 @@ msgstr "Gérer les compétences" msgid "Show current session status" msgstr "Afficher l’état actuel de la session" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Naviguer" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:530 -#: src/iac_code/commands/auth.py:562 src/iac_code/commands/auth.py:569 -#: src/iac_code/commands/auth.py:604 src/iac_code/commands/auth.py:611 -#: src/iac_code/commands/auth.py:632 src/iac_code/commands/auth.py:1107 -#: src/iac_code/commands/auth.py:1319 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmer" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:528 -#: src/iac_code/commands/auth.py:530 src/iac_code/commands/auth.py:562 -#: src/iac_code/commands/auth.py:569 src/iac_code/commands/auth.py:604 -#: src/iac_code/commands/auth.py:611 src/iac_code/commands/auth.py:632 -#: src/iac_code/commands/auth.py:1107 src/iac_code/commands/auth.py:1217 -#: src/iac_code/commands/auth.py:1319 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "Retour" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Keep" msgstr "Conserver" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Re-enter" msgstr "Saisir à nouveau" -#: src/iac_code/commands/auth.py:741 src/iac_code/commands/auth.py:853 -#: src/iac_code/commands/auth.py:911 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:946 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (actuel)" -#: src/iac_code/commands/auth.py:744 +#: src/iac_code/commands/auth.py:752 msgid "Custom model..." msgstr "Modèle personnalisé…" -#: src/iac_code/commands/auth.py:747 +#: src/iac_code/commands/auth.py:755 #, python-brace-format msgid "Select model for {provider}" msgstr "Sélectionner le modèle pour {provider}" -#: src/iac_code/commands/auth.py:749 +#: src/iac_code/commands/auth.py:757 msgid "Select model" msgstr "Sélectionner le modèle" -#: src/iac_code/commands/auth.py:757 +#: src/iac_code/commands/auth.py:765 msgid "Enter custom model name: " msgstr "Saisir le nom du modèle personnalisé : " -#: src/iac_code/commands/auth.py:783 +#: src/iac_code/commands/auth.py:791 msgid "Error: console not available" msgstr "Erreur : console indisponible" -#: src/iac_code/commands/auth.py:810 +#: src/iac_code/commands/auth.py:818 msgid "Configure LLM Provider" msgstr "Configurer le fournisseur LLM" -#: src/iac_code/commands/auth.py:811 +#: src/iac_code/commands/auth.py:819 msgid "Configure IaC Cloud Service" msgstr "Configurer le service cloud IaC" -#: src/iac_code/commands/auth.py:813 +#: src/iac_code/commands/auth.py:821 msgid "Select configuration type" msgstr "Sélectionner le type de configuration" -#: src/iac_code/commands/auth.py:815 src/iac_code/commands/auth.py:971 -#: src/iac_code/commands/auth.py:987 src/iac_code/commands/auth.py:1066 -#: src/iac_code/commands/auth.py:1258 src/iac_code/commands/auth.py:1299 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 +#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "Authentification annulée" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:951 -#: src/iac_code/commands/auth.py:1041 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "Sélectionner le fournisseur — {group}" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:909 -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "Tiers" -#: src/iac_code/commands/auth.py:867 +#: src/iac_code/commands/auth.py:875 #, python-brace-format msgid "{status}: {provider}" msgstr "{status} : {provider}" -#: src/iac_code/commands/auth.py:868 src/iac_code/commands/auth.py:1019 +#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 msgid "Configured" msgstr "Configuré" -#: src/iac_code/commands/auth.py:923 +#: src/iac_code/commands/auth.py:931 msgid "Select provider" msgstr "Sélectionner le fournisseur" -#: src/iac_code/commands/auth.py:964 +#: src/iac_code/commands/auth.py:972 #, python-brace-format msgid "Configure {provider}" msgstr "Configurer {provider}" -#: src/iac_code/commands/auth.py:980 +#: src/iac_code/commands/auth.py:988 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Saisir la clé API pour {provider}" -#: src/iac_code/commands/auth.py:1018 +#: src/iac_code/commands/auth.py:1026 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status} : {provider} / {model}" -#: src/iac_code/commands/auth.py:1027 src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1028 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1029 +#: src/iac_code/commands/auth.py:1037 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1030 +#: src/iac_code/commands/auth.py:1038 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1031 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1032 +#: src/iac_code/commands/auth.py:1040 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1033 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1034 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1035 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1037 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1039 +#: src/iac_code/commands/auth.py:1047 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1048 msgid "Compatible" msgstr "Compatible" -#: src/iac_code/commands/auth.py:1057 +#: src/iac_code/commands/auth.py:1065 msgid "Select Cloud Provider" msgstr "Sélectionner le fournisseur cloud" -#: src/iac_code/commands/auth.py:1073 +#: src/iac_code/commands/auth.py:1081 msgid "Credential" msgstr "Identifiants" -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 -#: src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 +#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Région" -#: src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1084 msgid "Configure Alibaba Cloud" msgstr "Configurer Alibaba Cloud" -#: src/iac_code/commands/auth.py:1178 +#: src/iac_code/commands/auth.py:1186 msgid "Current configuration" msgstr "Configuration actuelle" -#: src/iac_code/commands/auth.py:1180 +#: src/iac_code/commands/auth.py:1188 msgid "Mode" msgstr "Mode" -#: src/iac_code/commands/auth.py:1187 +#: src/iac_code/commands/auth.py:1194 msgid "(not set)" msgstr "(non défini)" -#: src/iac_code/commands/auth.py:1204 +#: src/iac_code/commands/auth.py:1203 +msgid "AccessKey" +msgstr "AccessKey" + +#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +msgid "STS Token" +msgstr "Jeton STS" + +#: src/iac_code/commands/auth.py:1207 +msgid "RAM Role" +msgstr "Rôle RAM" + +#: src/iac_code/commands/auth.py:1209 +msgid "OAuth Login (Browser)" +msgstr "Connexion OAuth (navigateur)" + +#: src/iac_code/commands/auth.py:1215 +msgid "AccessKey ID" +msgstr "ID AccessKey" + +#: src/iac_code/commands/auth.py:1216 +msgid "AccessKey Secret" +msgstr "Secret AccessKey" + +#: src/iac_code/commands/auth.py:1218 +msgid "RAM Role ARN" +msgstr "ARN du rôle RAM" + +#: src/iac_code/commands/auth.py:1219 +msgid "Session Name" +msgstr "Nom de session" + +#: src/iac_code/commands/auth.py:1220 +msgid "OAuth Site Type" +msgstr "Type de site OAuth" + +#: src/iac_code/commands/auth.py:1221 +msgid "OAuth Access Token" +msgstr "Jeton d'accès OAuth" + +#: src/iac_code/commands/auth.py:1222 +msgid "OAuth Refresh Token" +msgstr "Jeton de rafraîchissement OAuth" + +#: src/iac_code/commands/auth.py:1223 +msgid "OAuth Access Token Expire" +msgstr "Expiration du jeton d'accès OAuth" + +#: src/iac_code/commands/auth.py:1224 +msgid "OAuth Refresh Token Expire" +msgstr "Expiration du jeton de rafraîchissement OAuth" + +#: src/iac_code/commands/auth.py:1225 +msgid "STS Expiration" +msgstr "Expiration STS" + +#: src/iac_code/commands/auth.py:1382 +msgid "China" +msgstr "Chine" + +#: src/iac_code/commands/auth.py:1383 +msgid "International" +msgstr "International" + +#: src/iac_code/commands/auth.py:1385 +msgid "Choose site type" +msgstr "Choisir le type de site" + +#: src/iac_code/commands/auth.py:1400 +#, python-brace-format +msgid "Alibaba Cloud OAuth login failed: {error}" +msgstr "Échec de la connexion OAuth Alibaba Cloud : {error}" + +#: src/iac_code/commands/auth.py:1416 +msgid "Configured: Alibaba Cloud OAuth credentials saved" +msgstr "Configuré : identifiants OAuth Alibaba Cloud enregistrés" + +#: src/iac_code/commands/auth.py:1428 msgid "Configure Alibaba Cloud credentials" msgstr "Configurer les identifiants Alibaba Cloud" -#: src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1441 msgid "Reconfigure credential" msgstr "Reconfigurer les identifiants" -#: src/iac_code/commands/auth.py:1230 +#: src/iac_code/commands/auth.py:1454 msgid "Select credential type" msgstr "Sélectionner le type d’identifiants" -#: src/iac_code/commands/auth.py:1280 +#: src/iac_code/commands/auth.py:1510 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" +"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:1287 +#: src/iac_code/commands/auth.py:1517 msgid "Configure Alibaba Cloud region" msgstr "Configurer la région Alibaba Cloud" -#: src/iac_code/commands/auth.py:1313 +#: src/iac_code/commands/auth.py:1543 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "" -"Configured: Alibaba Cloud region saved to ~/.iac-codeConfigured: Alibaba " -"Cloud region saved to ~/.iac-codeConfiguration effectuée : région Alibaba" -" Cloud enregistrée dans ~/.iac-code" +"Configured: Alibaba Cloud region saved to ~/.iac-codeConfigured: Alibaba Cloud region saved to ~/.iac-" +"codeConfiguration effectuée : région Alibaba Cloud enregistrée dans ~/.iac-code" #: src/iac_code/commands/compact.py:12 msgid "Compact command requires a context." @@ -915,12 +910,10 @@ msgstr "Aucune boucle d’agent active." #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent_display} " -"reduction). Context usage: {usage_display}" +msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" msgstr "" -"Contexte compacté : {original} → {compacted} tokens (réduction " -"{percent_display}). Utilisation du contexte : {usage_display}" +"Contexte compacté : {original} → {compacted} tokens (réduction {percent_display}). Utilisation du contexte : " +"{usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -930,8 +923,7 @@ msgstr "La commande debug nécessite un contexte." msgid "No active session." msgstr "Aucune session active." -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 -#: src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "Aucun fournisseur configuré. Exécutez d’abord /auth." @@ -1025,12 +1017,10 @@ msgstr "Mémoire '{name}' supprimée." #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "" -"Model is managed by '{source}'. To change model, modify it in {source} or" -" switch provider via /auth." +msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." msgstr "" -"Le modèle est géré par '{source}'. Pour changer le modèle, modifiez-le " -"dans {source} ou changez de fournisseur via /auth." +"Le modèle est géré par '{source}'. Pour changer le modèle, modifiez-le dans {source} ou changez de fournisseur via " +"/auth." #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1096,8 +1086,7 @@ msgstr "Session" msgid "Provider" msgstr "Fournisseur" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 -#: src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 msgid "not configured" msgstr "non configuré" @@ -1198,10 +1187,9 @@ msgstr "Interrompu !" #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." msgstr "" -"Cannot determine provider for model: {model}. Run /auth to " -"configure.Cannot determine provider for model: {model}. Run /auth to " -"configure.Impossible de déterminer le fournisseur pour le modèle : " -"{model}. Exécutez /auth pour configurer." +"Cannot determine provider for model: {model}. Run /auth to configure.Cannot determine provider for model: {model}. " +"Run /auth to configure.Impossible de déterminer le fournisseur pour le modèle : {model}. Exécutez /auth pour " +"configurer." #: src/iac_code/providers/manager.py:95 #, python-brace-format @@ -1210,34 +1198,26 @@ msgstr "Clé de fournisseur inconnue : '{key}'. Exécutez /auth pour configurer. #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "" -"No API key configured for provider '{provider}' (model: {model}). Run " -"/auth to configure." -msgstr "" -"Aucune clé API configurée pour le fournisseur '{provider}' (modèle : " -"{model}). Exécutez /auth pour configurer." +msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." +msgstr "Aucune clé API configurée pour le fournisseur '{provider}' (modèle : {model}). Exécutez /auth pour configurer." #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct " -"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " -"suffix (e.g. {base_url}/v1)." +"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " +"endpoints require a /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"L’API n’a renvoyé aucune donnée. Vérifiez que votre API Base URL est " -"correcte (actuelle : {base_url}). De nombreux points de terminaison " -"compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." +"L’API n’a renvoyé aucune donnée. Vérifiez que votre API Base URL est correcte (actuelle : {base_url}). De nombreux " +"points de terminaison compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is " -"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" -" /v1 suffix (e.g. {base_url}/v1)." +"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" +"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"L’API a renvoyé une réponse invalide. Vérifiez que votre API Base URL est" -" correcte (actuelle : {base_url}). De nombreux points de terminaison " -"compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." +"L’API a renvoyé une réponse invalide. Vérifiez que votre API Base URL est correcte (actuelle : {base_url}). De " +"nombreux points de terminaison compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1318,30 +1298,127 @@ msgstr "Compatible Anthropic" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " -"support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " -"mode (remove 'llm_source: qwenpaw' from settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " +"settings.yml)." msgstr "" -"[Mode QwenPaw] Fournisseur inconnu '{provider_id}'. iac-code ne prend pas" -" en charge ce fournisseur.\n" +"[Mode QwenPaw] Fournisseur inconnu '{provider_id}'. iac-code ne prend pas en charge ce fournisseur.\n" "IDs de fournisseurs QwenPaw pris en charge : {supported_ids}\n" -"Solution : passez à un fournisseur pris en charge dans QwenPaw, ou " -"désactivez le mode QwenPaw (supprimez 'llm_source: qwenpaw' de " -"settings.yml)." +"Solution : passez à un fournisseur pris en charge dans QwenPaw, ou désactivez le mode QwenPaw (supprimez 'llm_source:" +" qwenpaw' de settings.yml)." #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "--permission-mode invalide : {!r}. Valeurs valides : {}" -#: src/iac_code/services/permissions/pipeline.py:54 -#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "Autoriser {} ?" +#: src/iac_code/services/providers/aliyun.py:144 +msgid "Alibaba Cloud OAuth site is missing." +msgstr "Le site OAuth Alibaba Cloud est manquant." + +#: src/iac_code/services/providers/aliyun.py:150 +msgid "Alibaba Cloud OAuth refresh token is missing." +msgstr "Le jeton de rafraîchissement OAuth Alibaba Cloud est manquant." + +#: src/iac_code/services/providers/aliyun.py:158 +msgid "Alibaba Cloud OAuth access token is missing." +msgstr "Le jeton d'accès OAuth Alibaba Cloud est manquant." + +#: src/iac_code/services/providers/aliyun_oauth.py:83 +msgid "Run /auth and choose OAuth Login (Browser)." +msgstr "Exécutez /auth et choisissez Connexion OAuth (navigateur)." + +#: src/iac_code/services/providers/aliyun_oauth.py:106 +#, python-brace-format +msgid "Unknown Aliyun OAuth site: {site_type}" +msgstr "Site OAuth Aliyun inconnu : {site_type}" + +#: src/iac_code/services/providers/aliyun_oauth.py:164 +msgid "Not found" +msgstr "Introuvable" + +#: src/iac_code/services/providers/aliyun_oauth.py:170 +msgid "invalid state" +msgstr "état invalide" + +#: src/iac_code/services/providers/aliyun_oauth.py:171 +msgid "Invalid state" +msgstr "État invalide" + +#: src/iac_code/services/providers/aliyun_oauth.py:176 +msgid "code not found" +msgstr "code d'autorisation introuvable" + +#: src/iac_code/services/providers/aliyun_oauth.py:177 +msgid "Authorization code not found" +msgstr "Code d'autorisation introuvable" + +#: src/iac_code/services/providers/aliyun_oauth.py:181 +msgid "Authorization successful. You can close this window." +msgstr "Autorisation réussie. Vous pouvez fermer cette fenêtre." + +#: src/iac_code/services/providers/aliyun_oauth.py:212 +#, python-brace-format +msgid "No available callback port in range {start}-{end}" +msgstr "Aucun port de callback disponible dans la plage {start}-{end}" + +#: src/iac_code/services/providers/aliyun_oauth.py:227 +msgid "OAuth login cancelled." +msgstr "Connexion OAuth annulée." + +#: src/iac_code/services/providers/aliyun_oauth.py:278 +msgid "Open in your browser:" +msgstr "Ouvrir dans le navigateur :" + +#: src/iac_code/services/providers/aliyun_oauth.py:299 +msgid "Waiting for browser authorization" +msgstr "En attente de l'autorisation du navigateur" + +#: src/iac_code/services/providers/aliyun_oauth.py:300 +msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." +msgstr "1. Le navigateur peut afficher official-cli ; il s'agit de l'application OAuth CLI officielle d'Alibaba Cloud." + +#: src/iac_code/services/providers/aliyun_oauth.py:302 +msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgstr "" +"2. Si une assignation est requise, assignez l'utilisateur RAM ou le rôle RAM actuellement connecté. Les groupes " +"d'utilisateurs ne sont pas pris en charge." + +#: src/iac_code/services/providers/aliyun_oauth.py:306 +msgid "" +"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " +"out of Alibaba Cloud and sign in again." +msgstr "" +"3. Après l'assignation, fermez l'ancienne page d'autorisation et relancez Connexion OAuth (navigateur). Si l'échec " +"persiste, déconnectez-vous d'Alibaba Cloud puis reconnectez-vous." + +#: src/iac_code/services/providers/aliyun_oauth.py:310 +msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgstr "" +"4. Les identifiants STS sont actualisés lorsque c'est possible jusqu'à leur expiration par Alibaba Cloud. Si " +"l'actualisation échoue, exécutez de nouveau /auth." + +#: src/iac_code/services/providers/aliyun_oauth.py:313 +msgid "Press Esc to cancel while waiting." +msgstr "Appuyez sur Échap pour annuler l'attente." + +#: src/iac_code/services/providers/aliyun_oauth.py:321 +msgid "" +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" +" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " +"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +msgstr "" +"Délai d'attente dépassé pour le callback OAuth. Si Alibaba Cloud vous a demandé d'assigner l'application official-" +"cli, assignez-la à l'utilisateur RAM ou au rôle RAM exact actuellement connecté. Les groupes d'utilisateurs ne sont " +"pas pris en charge. Fermez ensuite l'ancienne page d'autorisation, déconnectez-vous d'Alibaba Cloud et reconnectez-" +"vous si nécessaire, puis exécutez /auth pour choisir de nouveau Connexion OAuth (navigateur)." + #: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." @@ -1362,12 +1439,8 @@ msgid "Skill disabled: {name}" msgstr "Compétence désactivée : {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "" -"Review changed code for reuse, quality, and efficiency, then fix issues " -"found." -msgstr "" -"Examiner le code modifié pour la réutilisation, la qualité et " -"l’efficacité, puis corriger les problèmes détectés." +msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." +msgstr "Examiner le code modifié pour la réutilisation, la qualité et l’efficacité, puis corriger les problèmes détectés." #: src/iac_code/tools/edit_file.py:116 msgid "Edit" @@ -1465,9 +1538,8 @@ msgstr "L’URL ne peut pas être vide." #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Invalid" -" URL: missing scheme (e.g. http:// or https://). Got: {url}URL non valide" -" : schéma manquant (p. ex. http:// ou https://). Obtenu : {url}" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Invalid URL: missing scheme (e.g. http:// or " +"https://). Got: {url}URL non valide : schéma manquant (p. ex. http:// ou https://). Obtenu : {url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1576,8 +1648,7 @@ msgstr "Règle(s) de refus correspondante(s) : {}" msgid "matched ask rule(s): {}" msgstr "Règle(s) de demande correspondante(s) : {}" -#: src/iac_code/tools/bash/permissions.py:154 -#: src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "Règle(s) d'autorisation correspondante(s) : {}" @@ -1634,8 +1705,7 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "Appel de {action}…" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 -#: src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Appel réussi" @@ -1648,8 +1718,7 @@ msgstr "Réponse reçue ({count} lignes)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 -#: src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "Exécution de {action}…" @@ -1794,11 +1863,11 @@ msgstr "IMPORT TERMINÉ" msgid "IMPORT_FAILED" msgstr "ÉCHEC DE L’IMPORT" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:171 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:172 msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "Appel réussi (RequestId : {request_id})" @@ -1855,8 +1924,8 @@ msgstr "{count} documents trouvés (sur {total} au total)" #: src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py:143 msgid "Use web_fetch tool to read full document content if needed." msgstr "" -"Use web_fetch tool to read full document content if needed.Utilisez " -"l’outil web_fetch pour lire le document en entier si nécessaire." +"Use web_fetch tool to read full document content if needed.Utilisez l’outil web_fetch pour lire le document en entier" +" si nécessaire." #: src/iac_code/tools/cloud/aliyun/ros_stack.py:143 msgid "ROS Stack" @@ -1878,40 +1947,27 @@ msgid "" "Context:\n" "{context}" msgstr "" -"Erreur de syntaxe YAML dans le modèle (ligne {line}, colonne {col}) : " -"{problem}\n" +"Erreur de syntaxe YAML dans le modèle (ligne {line}, colonne {col}) : {problem}\n" "Contexte :\n" "{context}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:66 #, python-brace-format msgid "Template JSON syntax error (line {line}, column {col}): {msg}" -msgstr "" -"Erreur de syntaxe JSON dans le modèle (ligne {line}, colonne {col}) : " -"{msg}" +msgstr "Erreur de syntaxe JSON dans le modèle (ligne {line}, colonne {col}) : {msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "" -"Template {fmt} parse result is not an object (dict), please check the " -"template format" -msgstr "" -"Le résultat de l'analyse {fmt} du modèle n'est pas un objet (dict), " -"veuillez vérifier le format du modèle" +msgid "Template {fmt} parse result is not an object (dict), please check the template format" +msgstr "Le résultat de l'analyse {fmt} du modèle n'est pas un objet (dict), veuillez vérifier le format du modèle" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "" -"Template is missing ROSTemplateFormatVersion (ROS templates must include " -"this field, e.g. '2015-09-01')" -msgstr "" -"Le modèle ne contient pas ROSTemplateFormatVersion (les modèles ROS " -"doivent inclure ce champ, ex. '2015-09-01')" +msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" +msgstr "Le modèle ne contient pas ROSTemplateFormatVersion (les modèles ROS doivent inclure ce champ, ex. '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" -msgstr "" -"Le modèle ne contient pas Resources (les modèles ROS doivent inclure " -"Resources)" +msgstr "Le modèle ne contient pas Resources (les modèles ROS doivent inclure Resources)" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:112 #, python-brace-format @@ -1920,12 +1976,8 @@ msgstr "Resources doit être un objet (dict), le type actuel est {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "" -"Resource '{name}' definition must be an object (dict), current type is " -"{type}" -msgstr "" -"La définition de la ressource '{name}' doit être un objet (dict), le type" -" actuel est {type}" +msgid "Resource '{name}' definition must be an object (dict), current type is {type}" +msgstr "La définition de la ressource '{name}' doit être un objet (dict), le type actuel est {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 #, python-brace-format @@ -1935,17 +1987,11 @@ msgstr "La ressource '{name}' n'a pas le champ Type" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:129 #, python-brace-format msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" -msgstr "" -"La ressource '{name}' a le type incorrect '{wrong}', devrait être " -"'{correct}'" +msgstr "La ressource '{name}' a le type incorrect '{wrong}', devrait être '{correct}'" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "" -"Template structure validation found the following issues, please fix and " -"retry:" -msgstr "" -"La validation de la structure du modèle a détecté les problèmes suivants," -" veuillez corriger et réessayer :" +msgid "Template structure validation found the following issues, please fix and retry:" +msgstr "La validation de la structure du modèle a détecté les problèmes suivants, veuillez corriger et réessayer :" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 #, python-brace-format @@ -1981,14 +2027,12 @@ msgstr "Mode debug" msgid "Log file" msgstr "Fichier journal" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 -#: src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "Réflexion pendant {seconds:.1f}s" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 -#: src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o pour développer)" @@ -2016,9 +2060,7 @@ msgstr "Terminé ({child_count} utilisations d’outil{token_info}{elapsed})" #: src/iac_code/ui/renderer.py:572 #, python-brace-format msgid "+ {count} more tool uses (ctrl+o to expand)" -msgstr "" -"+ {count} more tool uses (ctrl+o to expand)+ {count} utilisations d’outil" -" supplémentaires (ctrl+o pour développer)" +msgstr "+ {count} more tool uses (ctrl+o to expand)+ {count} utilisations d’outil supplémentaires (ctrl+o pour développer)" #: src/iac_code/ui/renderer.py:1177 #, python-brace-format @@ -2114,9 +2156,7 @@ msgstr "Ignorer jusqu’à la prochaine version" #: src/iac_code/ui/repl.py:506 msgid "Hide this update until a newer version is available." -msgstr "" -"Masquer cette mise à jour jusqu’à ce qu’une version plus récente soit " -"disponible." +msgstr "Masquer cette mise à jour jusqu’à ce qu’une version plus récente soit disponible." #: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 msgid "Update command failed. Continuing with the current version." @@ -2141,16 +2181,14 @@ msgstr "La prise en charge des commandes shell n'est pas disponible." #: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." -msgstr "" -"Compétence inconnue : ${name}. Tapez / pour lister les commandes et les " -"compétences." +msgstr "Compétence inconnue : ${name}. Tapez / pour lister les commandes et les compétences." #: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" -"Unknown command: /{name}. Type /help for available commands.Commande " -"inconnue : /{name}. Saisissez /help pour la liste des commandes." +"Unknown command: /{name}. Type /help for available commands.Commande inconnue : /{name}. Saisissez /help pour la " +"liste des commandes." #: src/iac_code/ui/repl.py:833 #, python-brace-format @@ -2197,12 +2235,10 @@ msgstr "(Commande copiée dans le presse-papiers)" #: src/iac_code/ui/repl.py:1451 #, python-brace-format -msgid "" -"Current model {model} does not support image input. Use /model to switch " -"to a vision-capable model." +msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." msgstr "" -"Le modèle actuel {model} ne prend pas en charge l’entrée d’image. " -"Utilisez /model pour passer à un modèle compatible vision." +"Le modèle actuel {model} ne prend pas en charge l’entrée d’image. Utilisez /model pour passer à un modèle compatible " +"vision." #: src/iac_code/ui/repl.py:1460 #, python-brace-format @@ -2210,12 +2246,8 @@ msgid "Image error: {err}" msgstr "Erreur d’image : {err}" #: src/iac_code/ui/repl.py:1477 -msgid "" -"Failed to persist image to cache; it will only exist in memory for this " -"turn." -msgstr "" -"Impossible de persister l’image dans le cache ; elle n’existera qu’en " -"mémoire pour ce tour." +msgid "Failed to persist image to cache; it will only exist in memory for this turn." +msgstr "Impossible de persister l’image dans le cache ; elle n’existera qu’en mémoire pour ce tour." #: src/iac_code/ui/spinner.py:52 msgid "Processing" @@ -2405,12 +2437,8 @@ msgstr "{current} sur {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "" -"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " -"cancel" -msgstr "" -"{count} compétences - Espace pour activer/désactiver, Entrée pour " -"enregistrer, Tab pour trier, Échap pour annuler" +msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" +msgstr "{count} compétences - Espace pour activer/désactiver, Entrée pour enregistrer, Tab pour trier, Échap pour annuler" #: src/iac_code/ui/dialogs/skills_picker.py:171 #, python-brace-format @@ -2496,12 +2524,8 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code sous Windows nécessite Git for Windows." #: src/iac_code/utils/platform.py:41 -msgid "" -"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " -"variable." -msgstr "" -"S'il est installé mais absent du PATH, définissez la variable " -"d'environnement IAC_CODE_GIT_BASH_PATH." +msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." +msgstr "S'il est installé mais absent du PATH, définissez la variable d'environnement IAC_CODE_GIT_BASH_PATH." #: src/iac_code/utils/platform.py:44 msgid "To install:" @@ -2512,12 +2536,8 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " Option 1 - winget (nécessite l'accès à github.com) :" #: src/iac_code/utils/platform.py:52 -msgid "" -" Option 2 - if you cannot reach github.com, run this to install via " -"npmmirror:" -msgstr "" -" Option 2 - si vous ne pouvez pas accéder à github.com, exécutez ceci " -"pour installer via npmmirror :" +msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" +msgstr " Option 2 - si vous ne pouvez pas accéder à github.com, exécutez ceci pour installer via npmmirror :" #~ msgid "DashScope Token Plan" #~ msgstr "DashScope Token Plan" @@ -2525,14 +2545,8 @@ msgstr "" #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "" -#~ "LLM provider is locked by '{source}'." -#~ " To change, modify llm_source in " -#~ "settings.yml." -#~ msgstr "" -#~ "Le fournisseur LLM est verrouillé par" -#~ " '{source}'. Pour changer, modifiez " -#~ "llm_source dans settings.yml." +#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." +#~ msgstr "Le fournisseur LLM est verrouillé par '{source}'. Pour changer, modifiez llm_source dans settings.yml." #~ msgid "Provider switched: {status}" #~ msgstr "Provider changé : {status}" @@ -2549,13 +2563,9 @@ msgstr "" #~ msgid "Cache create" #~ msgstr "Création du cache" -#~ msgid "" -#~ "{count} skills - Space to toggle, " -#~ "Enter to save, / to search, t " -#~ "to sort, Esc to cancel" +#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" #~ msgstr "" -#~ "{count} compétences - Espace pour " -#~ "activer/désactiver, Entrée pour enregistrer, /" -#~ " pour rechercher, t pour trier, Échap" -#~ " pour annuler" +#~ "{count} compétences - Espace pour activer/désactiver, Entrée pour " +#~ "enregistrer, / pour rechercher, t pour trier, Échap pour " +#~ "annuler" 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 9d7ef47..fe25d6b 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:45+0800\n" +"POT-Creation-Date: 2026-06-02 21:26+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: ja\n" @@ -23,18 +23,12 @@ msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): { msgstr "無効な IAC_CODE_PROVIDER 値: {!r}。有効な値 (大文字と小文字を区別しません): {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "" -"Unix domain socket transport is not supported on Windows. Use --transport" -" http or --transport stdio instead." -msgstr "" -"Unix ドメインソケットトランスポートは Windows ではサポートされていません。--transport http または " -"--transport stdio を使用してください。" +msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." +msgstr "Unix ドメインソケットトランスポートは Windows ではサポートされていません。--transport http または --transport stdio を使用してください。" #: src/iac_code/acp/slash_registry.py:44 #, python-brace-format -msgid "" -"Command '/{cmd_name}' is not supported over ACP. Supported commands: " -"{supported}" +msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" msgstr "コマンド '/{cmd_name}' は ACP 上ではサポートされていません。サポートされているコマンド:{supported}" #: src/iac_code/acp/slash_registry.py:58 @@ -53,9 +47,7 @@ msgstr "圧縮できる内容がありません:会話が空です。" #: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "" -"Conversation too short to compact: all messages are within the recent " -"{turns}-turn preservation window." +msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." msgstr "会話が短すぎて圧縮できません:すべてのメッセージが直近 {turns} ターンの保持ウィンドウ内にあります。" #: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 @@ -64,12 +56,8 @@ msgstr "圧縮に失敗しました。詳細はログをご確認ください。 #: src/iac_code/acp/slash_registry.py:85 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent} reduction)." -" Context usage: {usage}" -msgstr "" -"コンテキストを圧縮しました:{original} → {compacted} tokens({percent} 削減)。 " -"コンテキスト使用量:{usage}" +msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" +msgstr "コンテキストを圧縮しました:{original} → {compacted} tokens({percent} 削減)。 コンテキスト使用量:{usage}" #: src/iac_code/acp/slash_registry.py:99 #, python-brace-format @@ -106,8 +94,8 @@ msgstr "使用方法:/debug [on|off]" msgid "Memory manager is unavailable." msgstr "メモリマネージャーを利用できません。" -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "権限が拒否されました。" @@ -124,8 +112,7 @@ msgstr "探索" msgid "Plan" msgstr "計画" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 -#: src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "エージェント" @@ -177,16 +164,14 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-" -"code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " 修正方法:iac-code を実行し、/auth と入力してください\n" " または環境変数を設定:IAC_CODE_API_KEY=<あなたのキー>\n" -" ドキュメント:https://aliyun.github.io/iac-" -"code/ja/docs/configuration/authentication\n" +" ドキュメント:https://aliyun.github.io/iac-code/ja/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -208,11 +193,9 @@ msgstr "インストールに失敗しました(PowerShell 終了コード {} #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may " -"have been cancelled or the installer used a non-standard path." -msgstr "" -"インストーラーは終了しましたが、一般的な場所に bash.exe が見つかりませんでした。UAC " -"がキャンセルされたか、インストーラーが標準外のパスを使用した可能性があります。" +"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " +"non-standard path." +msgstr "インストーラーは終了しましたが、一般的な場所に bash.exe が見つかりませんでした。UAC がキャンセルされたか、インストーラーが標準外のパスを使用した可能性があります。" #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -235,11 +218,8 @@ msgstr "npmmirror ミラー経由で Git for Windows をインストールしま msgid "YAML config file containing A2A client options" msgstr "A2A クライアントオプションを含む YAML 設定ファイル" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 -#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "" -"A2A client dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" msgstr "A2A クライアントの依存関係が不足しています。次のコマンドでインストールしてください: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 @@ -258,8 +238,7 @@ msgstr "出力形式:text、json、stream-json" msgid "Maximum agent turns in headless mode" msgstr "ヘッドレスモードでの最大エージェンターン数" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 -#: src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "デバッグログを有効にする" @@ -284,15 +263,11 @@ msgid "Install completion for the current shell." msgstr "現在の shell に補完をインストールします。" #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "" -"Show completion for the current shell, to copy it or customize the " -"installation." +msgid "Show completion for the current shell, to copy it or customize the installation." msgstr "現在の shell 向けの補完スクリプトを表示します。コピーしたり、インストールをカスタマイズしたりできます。" #: src/iac_code/cli/main.py:110 -msgid "" -"Comma-separated tool permission patterns to allow, e.g. 'bash(git " -"*),write_file'" +msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" msgstr "許可するツール権限パターン(カンマ区切り)、例: 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 @@ -337,39 +312,27 @@ msgid "YAML config file for A2A server options" msgstr "A2A サーバーオプション用の YAML 設定ファイル" #: src/iac_code/cli/main.py:584 -msgid "" -"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " -"not a registered A2A port." -msgstr "" -"HTTP サーバーポート。41242 は Gemini CLI に触発された iac-code のデフォルトで、登録済みの A2A " -"ポートではありません。" +msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." +msgstr "HTTP サーバーポート。41242 は Gemini CLI に触発された iac-code のデフォルトで、登録済みの A2A ポートではありません。" #: src/iac_code/cli/main.py:589 -msgid "" -"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " -"redis-streams" +msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" msgstr "A2A トランスポート: http、stdio、unix、websocket、grpc、grpc-jsonrpc、または redis-streams" #: src/iac_code/cli/main.py:595 -msgid "" -"Expose A2A thinking signal types; repeat for multiple. Values: raw-" -"thinking, tool-trace." +msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." msgstr "A2A thinking 信号タイプを公開します。複数指定するには繰り返します。値:raw-thinking、tool-trace。" #: src/iac_code/cli/main.py:646 -msgid "" -"A2A server dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" +msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" msgstr "A2A サーバーの依存関係が不足しています。次のコマンドでインストールしてください: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "A2A JSON-RPC エンドポイントにプロンプトを送信します。" -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 -#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 -#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "A2A JSON-RPC エンドポイント URL" @@ -394,48 +357,33 @@ msgstr "リクエストと共に送信する作業ディレクトリのメタデ msgid "A2A context ID to continue" msgstr "継続する A2A コンテキスト ID" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 -#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 -#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 -#: src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "A2A HTTP リクエスト用の Bearer トークン" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 -#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 -#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 -#: src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "A2A HTTP リクエスト用の Basic 認証ユーザー名" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 -#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 -#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 -#: src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "A2A HTTP リクエスト用の Basic 認証パスワード" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 -#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 -#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 -#: src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "A2A HTTP リクエスト用の API キー" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 -#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 -#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 -#: src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "A2A API キー用の HTTP ヘッダー名" @@ -471,10 +419,8 @@ msgstr "A2A エージェントのベース URL" msgid "Get an A2A task." msgstr "A2A タスクを取得します。" -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 -#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 -#: src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "A2A タスク ID" @@ -522,8 +468,7 @@ msgstr "A2A タスクのイベントストリームを購読します。" msgid "Create an A2A task push notification config." msgstr "A2A タスクプッシュ通知設定を作成します。" -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 -#: src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "プッシュ設定 ID" @@ -647,229 +592,295 @@ msgstr "スキルを管理" msgid "Show current session status" msgstr "現在のセッション状態を表示" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "移動" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:530 -#: src/iac_code/commands/auth.py:562 src/iac_code/commands/auth.py:569 -#: src/iac_code/commands/auth.py:604 src/iac_code/commands/auth.py:611 -#: src/iac_code/commands/auth.py:632 src/iac_code/commands/auth.py:1107 -#: src/iac_code/commands/auth.py:1319 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "確認" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:528 -#: src/iac_code/commands/auth.py:530 src/iac_code/commands/auth.py:562 -#: src/iac_code/commands/auth.py:569 src/iac_code/commands/auth.py:604 -#: src/iac_code/commands/auth.py:611 src/iac_code/commands/auth.py:632 -#: src/iac_code/commands/auth.py:1107 src/iac_code/commands/auth.py:1217 -#: src/iac_code/commands/auth.py:1319 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "戻る" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Keep" msgstr "維持" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Re-enter" msgstr "再入力" -#: src/iac_code/commands/auth.py:741 src/iac_code/commands/auth.py:853 -#: src/iac_code/commands/auth.py:911 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:946 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (現在)" -#: src/iac_code/commands/auth.py:744 +#: src/iac_code/commands/auth.py:752 msgid "Custom model..." msgstr "カスタムモデル…" -#: src/iac_code/commands/auth.py:747 +#: src/iac_code/commands/auth.py:755 #, python-brace-format msgid "Select model for {provider}" msgstr "{provider} のモデルを選択してください" -#: src/iac_code/commands/auth.py:749 +#: src/iac_code/commands/auth.py:757 msgid "Select model" msgstr "モデルを選択" -#: src/iac_code/commands/auth.py:757 +#: src/iac_code/commands/auth.py:765 msgid "Enter custom model name: " msgstr "カスタムモデル名を入力してください:" -#: src/iac_code/commands/auth.py:783 +#: src/iac_code/commands/auth.py:791 msgid "Error: console not available" msgstr "エラー:コンソールを使用できません" -#: src/iac_code/commands/auth.py:810 +#: src/iac_code/commands/auth.py:818 msgid "Configure LLM Provider" msgstr "LLM プロバイダーを設定" -#: src/iac_code/commands/auth.py:811 +#: src/iac_code/commands/auth.py:819 msgid "Configure IaC Cloud Service" msgstr "IaC クラウドサービスを設定" -#: src/iac_code/commands/auth.py:813 +#: src/iac_code/commands/auth.py:821 msgid "Select configuration type" msgstr "設定の種類を選択" -#: src/iac_code/commands/auth.py:815 src/iac_code/commands/auth.py:971 -#: src/iac_code/commands/auth.py:987 src/iac_code/commands/auth.py:1066 -#: src/iac_code/commands/auth.py:1258 src/iac_code/commands/auth.py:1299 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 +#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "認証をキャンセルしました" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:951 -#: src/iac_code/commands/auth.py:1041 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "プロバイダーを選択 — {group}" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:909 -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "サードパーティ" -#: src/iac_code/commands/auth.py:867 +#: src/iac_code/commands/auth.py:875 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}:{provider}" -#: src/iac_code/commands/auth.py:868 src/iac_code/commands/auth.py:1019 +#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 msgid "Configured" msgstr "設定済み" -#: src/iac_code/commands/auth.py:923 +#: src/iac_code/commands/auth.py:931 msgid "Select provider" msgstr "プロバイダーを選択" -#: src/iac_code/commands/auth.py:964 +#: src/iac_code/commands/auth.py:972 #, python-brace-format msgid "Configure {provider}" msgstr "{provider} を設定" -#: src/iac_code/commands/auth.py:980 +#: src/iac_code/commands/auth.py:988 #, python-brace-format msgid "Enter API key for {provider}" msgstr "{provider} の API key を入力してください" -#: src/iac_code/commands/auth.py:1018 +#: src/iac_code/commands/auth.py:1026 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}:{provider} / {model}" -#: src/iac_code/commands/auth.py:1027 src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1028 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1029 +#: src/iac_code/commands/auth.py:1037 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1030 +#: src/iac_code/commands/auth.py:1038 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1031 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1032 +#: src/iac_code/commands/auth.py:1040 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1033 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1034 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1035 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1037 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1039 +#: src/iac_code/commands/auth.py:1047 msgid "Local" msgstr "ローカル" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1048 msgid "Compatible" msgstr "互換モード" -#: src/iac_code/commands/auth.py:1057 +#: src/iac_code/commands/auth.py:1065 msgid "Select Cloud Provider" msgstr "クラウドプロバイダーを選択" -#: src/iac_code/commands/auth.py:1073 +#: src/iac_code/commands/auth.py:1081 msgid "Credential" msgstr "クレデンシャル" -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 -#: src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 +#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "リージョン" -#: src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1084 msgid "Configure Alibaba Cloud" msgstr "Alibaba Cloud を設定" -#: src/iac_code/commands/auth.py:1178 +#: src/iac_code/commands/auth.py:1186 msgid "Current configuration" msgstr "現在の設定" -#: src/iac_code/commands/auth.py:1180 +#: src/iac_code/commands/auth.py:1188 msgid "Mode" msgstr "モード" -#: src/iac_code/commands/auth.py:1187 +#: src/iac_code/commands/auth.py:1194 msgid "(not set)" msgstr "(未設定)" -#: src/iac_code/commands/auth.py:1204 +#: src/iac_code/commands/auth.py:1203 +msgid "AccessKey" +msgstr "AccessKey" + +#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +msgid "STS Token" +msgstr "STS トークン" + +#: src/iac_code/commands/auth.py:1207 +msgid "RAM Role" +msgstr "RAM ロール" + +#: src/iac_code/commands/auth.py:1209 +msgid "OAuth Login (Browser)" +msgstr "OAuth ログイン(ブラウザー)" + +#: src/iac_code/commands/auth.py:1215 +msgid "AccessKey ID" +msgstr "AccessKey ID" + +#: src/iac_code/commands/auth.py:1216 +msgid "AccessKey Secret" +msgstr "AccessKey シークレット" + +#: src/iac_code/commands/auth.py:1218 +msgid "RAM Role ARN" +msgstr "RAM ロール ARN" + +#: src/iac_code/commands/auth.py:1219 +msgid "Session Name" +msgstr "セッション名" + +#: src/iac_code/commands/auth.py:1220 +msgid "OAuth Site Type" +msgstr "OAuth サイトタイプ" + +#: src/iac_code/commands/auth.py:1221 +msgid "OAuth Access Token" +msgstr "OAuth アクセストークン" + +#: src/iac_code/commands/auth.py:1222 +msgid "OAuth Refresh Token" +msgstr "OAuth リフレッシュトークン" + +#: src/iac_code/commands/auth.py:1223 +msgid "OAuth Access Token Expire" +msgstr "OAuth アクセストークンの有効期限" + +#: src/iac_code/commands/auth.py:1224 +msgid "OAuth Refresh Token Expire" +msgstr "OAuth リフレッシュトークンの有効期限" + +#: src/iac_code/commands/auth.py:1225 +msgid "STS Expiration" +msgstr "STS 有効期限" + +#: src/iac_code/commands/auth.py:1382 +msgid "China" +msgstr "中国" + +#: src/iac_code/commands/auth.py:1383 +msgid "International" +msgstr "国際" + +#: src/iac_code/commands/auth.py:1385 +msgid "Choose site type" +msgstr "サイトタイプを選択" + +#: src/iac_code/commands/auth.py:1400 +#, python-brace-format +msgid "Alibaba Cloud OAuth login failed: {error}" +msgstr "Alibaba Cloud OAuth ログインに失敗しました: {error}" + +#: src/iac_code/commands/auth.py:1416 +msgid "Configured: Alibaba Cloud OAuth credentials saved" +msgstr "設定完了: Alibaba Cloud OAuth 認証情報を保存しました" + +#: src/iac_code/commands/auth.py:1428 msgid "Configure Alibaba Cloud credentials" msgstr "Alibaba Cloud のクレデンシャルを設定" -#: src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1441 msgid "Reconfigure credential" msgstr "クレデンシャルを再設定" -#: src/iac_code/commands/auth.py:1230 +#: src/iac_code/commands/auth.py:1454 msgid "Select credential type" msgstr "クレデンシャルの種類を選択" -#: src/iac_code/commands/auth.py:1280 +#: src/iac_code/commands/auth.py:1510 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" -msgstr "" -"Configured: Alibaba Cloud credentials saved to ~/.iac-code設定しました:Alibaba " -"Cloud のクレデンシャルを ~/.iac-code に保存しました" +msgstr "Configured: Alibaba Cloud credentials saved to ~/.iac-code設定しました:Alibaba Cloud のクレデンシャルを ~/.iac-code に保存しました" -#: src/iac_code/commands/auth.py:1287 +#: src/iac_code/commands/auth.py:1517 msgid "Configure Alibaba Cloud region" msgstr "Alibaba Cloud のリージョンを設定" -#: src/iac_code/commands/auth.py:1313 +#: src/iac_code/commands/auth.py:1543 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "設定しました:Alibaba Cloud のリージョンを ~/.iac-code に保存しました" @@ -887,12 +898,8 @@ msgstr "アクティブなエージェントループがありません。" #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent_display} " -"reduction). Context usage: {usage_display}" -msgstr "" -"コンテキストを圧縮しました:{original} → {compacted} tokens({percent_display} 削減)。 " -"コンテキスト使用量:{usage_display}" +msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" +msgstr "コンテキストを圧縮しました:{original} → {compacted} tokens({percent_display} 削減)。 コンテキスト使用量:{usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -902,8 +909,7 @@ msgstr "debug コマンドにはコンテキストが必要です。" msgid "No active session." msgstr "アクティブなセッションがありません。" -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 -#: src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "設定済みのプロバイダーがありません。先に /auth を実行してください。" @@ -997,12 +1003,8 @@ msgstr "メモリ '{name}' を削除しました。" #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "" -"Model is managed by '{source}'. To change model, modify it in {source} or" -" switch provider via /auth." -msgstr "" -"モデルは '{source}' によって管理されています。モデルを変更するには {source} で調整するか、/auth " -"で別のプロバイダーに切り替えてください。" +msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." +msgstr "モデルは '{source}' によって管理されています。モデルを変更するには {source} で調整するか、/auth で別のプロバイダーに切り替えてください。" #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1068,8 +1070,7 @@ msgstr "セッション" msgid "Provider" msgstr "プロバイダー" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 -#: src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 msgid "not configured" msgstr "未設定" @@ -1169,9 +1170,7 @@ msgstr "中断しました。" #: src/iac_code/providers/manager.py:78 #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." -msgstr "" -"Cannot determine provider for model: {model}. Run /auth to configure.モデル " -"{model} のプロバイダーを特定できません。/auth を実行して設定してください。" +msgstr "Cannot determine provider for model: {model}. Run /auth to configure.モデル {model} のプロバイダーを特定できません。/auth を実行して設定してください。" #: src/iac_code/providers/manager.py:95 #, python-brace-format @@ -1180,30 +1179,22 @@ msgstr "不明なプロバイダーキー:'{key}'。/auth を実行して設 #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "" -"No API key configured for provider '{provider}' (model: {model}). Run " -"/auth to configure." +msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." msgstr "プロバイダー '{provider}' の API キーが設定されていません(モデル: {model})。/auth を実行して設定してください。" #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct " -"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " -"suffix (e.g. {base_url}/v1)." -msgstr "" -"API からデータが返りませんでした。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI " -"互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" +"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " +"endpoints require a /v1 suffix (e.g. {base_url}/v1)." +msgstr "API からデータが返りませんでした。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI 互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is " -"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" -" /v1 suffix (e.g. {base_url}/v1)." -msgstr "" -"API から無効な応答が返りました。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI " -"互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" +"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" +"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." +msgstr "API から無効な応答が返りました。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI 互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1284,29 +1275,119 @@ msgstr "Anthropic 互換" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " -"support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " -"mode (remove 'llm_source: qwenpaw' from settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " +"settings.yml)." msgstr "" -"[QwenPaw モード] プロバイダー '{provider_id}' を認識できません。iac-code " -"はこのプロバイダーをサポートしていません。\n" +"[QwenPaw モード] プロバイダー '{provider_id}' を認識できません。iac-code はこのプロバイダーをサポートしていません。\n" "サポートされている QwenPaw プロバイダー ID:{supported_ids}\n" -"対処法:QwenPaw でサポートされているプロバイダーに切り替えるか、QwenPaw モードを無効にしてください(settings.yml から" -" 'llm_source: qwenpaw' を削除)。" +"対処法:QwenPaw でサポートされているプロバイダーに切り替えるか、QwenPaw モードを無効にしてください(settings.yml から 'llm_source: qwenpaw' を削除)。" #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "無効な --permission-mode {!r} です。有効な値: {}" -#: src/iac_code/services/permissions/pipeline.py:54 -#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "{} を許可しますか?" +#: src/iac_code/services/providers/aliyun.py:144 +msgid "Alibaba Cloud OAuth site is missing." +msgstr "Alibaba Cloud OAuth サイトがありません。" + +#: src/iac_code/services/providers/aliyun.py:150 +msgid "Alibaba Cloud OAuth refresh token is missing." +msgstr "Alibaba Cloud OAuth リフレッシュトークンがありません。" + +#: src/iac_code/services/providers/aliyun.py:158 +msgid "Alibaba Cloud OAuth access token is missing." +msgstr "Alibaba Cloud OAuth アクセストークンがありません。" + +#: src/iac_code/services/providers/aliyun_oauth.py:83 +msgid "Run /auth and choose OAuth Login (Browser)." +msgstr "/auth を実行し、OAuth ログイン(ブラウザー)を選択してください。" + +#: src/iac_code/services/providers/aliyun_oauth.py:106 +#, python-brace-format +msgid "Unknown Aliyun OAuth site: {site_type}" +msgstr "不明な Aliyun OAuth サイト: {site_type}" + +#: src/iac_code/services/providers/aliyun_oauth.py:164 +msgid "Not found" +msgstr "見つかりません" + +#: src/iac_code/services/providers/aliyun_oauth.py:170 +msgid "invalid state" +msgstr "無効な状態" + +#: src/iac_code/services/providers/aliyun_oauth.py:171 +msgid "Invalid state" +msgstr "無効な状態" + +#: src/iac_code/services/providers/aliyun_oauth.py:176 +msgid "code not found" +msgstr "認可コードが見つかりません" + +#: src/iac_code/services/providers/aliyun_oauth.py:177 +msgid "Authorization code not found" +msgstr "認可コードが見つかりません" + +#: src/iac_code/services/providers/aliyun_oauth.py:181 +msgid "Authorization successful. You can close this window." +msgstr "認可に成功しました。このウィンドウを閉じてもかまいません。" + +#: src/iac_code/services/providers/aliyun_oauth.py:212 +#, python-brace-format +msgid "No available callback port in range {start}-{end}" +msgstr "{start}-{end} の範囲に利用可能なコールバックポートがありません" + +#: src/iac_code/services/providers/aliyun_oauth.py:227 +msgid "OAuth login cancelled." +msgstr "OAuth ログインをキャンセルしました。" + +#: src/iac_code/services/providers/aliyun_oauth.py:278 +msgid "Open in your browser:" +msgstr "ブラウザーで開く:" + +#: src/iac_code/services/providers/aliyun_oauth.py:299 +msgid "Waiting for browser authorization" +msgstr "ブラウザー認可を待機しています" + +#: src/iac_code/services/providers/aliyun_oauth.py:300 +msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." +msgstr "1. ブラウザーに official-cli と表示される場合があります。これは Alibaba Cloud 公式 CLI OAuth アプリケーションです。" + +#: src/iac_code/services/providers/aliyun_oauth.py:302 +msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgstr "2. 割り当てが必要な場合は、サインイン中の RAM ユーザーまたは RAM ロールを割り当ててください。ユーザーグループはサポートされていません。" + +#: src/iac_code/services/providers/aliyun_oauth.py:306 +msgid "" +"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " +"out of Alibaba Cloud and sign in again." +msgstr "3. 割り当て後、古い認可ページを閉じて OAuth ログイン(ブラウザー)を再実行してください。それでも失敗する場合は、Alibaba Cloud からサインアウトして再度サインインしてください。" + +#: src/iac_code/services/providers/aliyun_oauth.py:310 +msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgstr "4. STS 認証情報は Alibaba Cloud が期限切れにするまで可能な場合に更新されます。更新に失敗した場合は、/auth を再実行してください。" + +#: src/iac_code/services/providers/aliyun_oauth.py:313 +msgid "Press Esc to cancel while waiting." +msgstr "Esc を押すと待機をキャンセルできます。" + +#: src/iac_code/services/providers/aliyun_oauth.py:321 +msgid "" +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" +" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " +"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +msgstr "" +"OAuth コールバックの待機がタイムアウトしました。Alibaba Cloud から official-cli アプリケーションの割り当てを求められた場合は、現在サインインしている正確な RAM ユーザーまたは RAM " +"ロールに割り当ててください。ユーザーグループはサポートされていません。その後、古い認可ページを閉じ、必要に応じて Alibaba Cloud からサインアウトしてサインインし直し、/auth を実行して OAuth " +"ログイン(ブラウザー)を再度選択してください。" + #: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." @@ -1327,9 +1408,7 @@ msgid "Skill disabled: {name}" msgstr "スキルが無効です: {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "" -"Review changed code for reuse, quality, and efficiency, then fix issues " -"found." +msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." msgstr "変更されたコードの再利用性、品質、効率を確認し、見つかった問題を修正してください。" #: src/iac_code/tools/edit_file.py:116 @@ -1427,9 +1506,7 @@ msgstr "URL を空にすることはできません。" #: src/iac_code/tools/web_fetch.py:100 #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" -msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}無効な " -"URL:スキームがありません(例:http:// または https://)。値:{url}" +msgstr "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}無効な URL:スキームがありません(例:http:// または https://)。値:{url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1538,8 +1615,7 @@ msgstr "一致した拒否ルール: {}" msgid "matched ask rule(s): {}" msgstr "一致した確認ルール: {}" -#: src/iac_code/tools/bash/permissions.py:154 -#: src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "一致した許可ルール: {}" @@ -1596,8 +1672,7 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "{action} を呼び出しています…" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 -#: src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "呼び出しに成功しました" @@ -1610,8 +1685,7 @@ msgstr "応答を受信しました({count} 行)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 -#: src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "{action} を実行中…" @@ -1756,11 +1830,11 @@ msgstr "インポート完了" msgid "IMPORT_FAILED" msgstr "インポート失敗" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:171 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:172 msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "呼び出しに成功しました(RequestId: {request_id})" @@ -1849,18 +1923,12 @@ msgstr "テンプレートの JSON 構文エラー({line} 行目、{col} 列 #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "" -"Template {fmt} parse result is not an object (dict), please check the " -"template format" +msgid "Template {fmt} parse result is not an object (dict), please check the template format" msgstr "テンプレートの {fmt} 解析結果がオブジェクト(dict)ではありません。テンプレートの形式を確認してください" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "" -"Template is missing ROSTemplateFormatVersion (ROS templates must include " -"this field, e.g. '2015-09-01')" -msgstr "" -"テンプレートに ROSTemplateFormatVersion がありません(ROS テンプレートにはこのフィールドが必須です。例: " -"'2015-09-01')" +msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" +msgstr "テンプレートに ROSTemplateFormatVersion がありません(ROS テンプレートにはこのフィールドが必須です。例: '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" @@ -1873,9 +1941,7 @@ msgstr "Resources はオブジェクト(dict)でなければなりません #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "" -"Resource '{name}' definition must be an object (dict), current type is " -"{type}" +msgid "Resource '{name}' definition must be an object (dict), current type is {type}" msgstr "リソース '{name}' の定義はオブジェクト(dict)でなければなりません。現在の型は {type} です" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 @@ -1889,9 +1955,7 @@ msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" msgstr "リソース '{name}' の型 '{wrong}' が正しくありません。'{correct}' に変更してください" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "" -"Template structure validation found the following issues, please fix and " -"retry:" +msgid "Template structure validation found the following issues, please fix and retry:" msgstr "テンプレート構造の検証で以下の問題が見つかりました。修正して再試行してください:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 @@ -1928,14 +1992,12 @@ msgstr "デバッグモード" msgid "Log file" msgstr "ログファイル" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 -#: src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "{seconds:.1f} 秒考えました" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 -#: src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o で展開)" @@ -2089,9 +2151,7 @@ msgstr "不明なスキル: ${name}。/ を入力するとコマンドとスキ #: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." -msgstr "" -"Unknown command: /{name}. Type /help for available " -"commands.不明なコマンドです:/{name}。利用可能なコマンドは /help を入力してください。" +msgstr "Unknown command: /{name}. Type /help for available commands.不明なコマンドです:/{name}。利用可能なコマンドは /help を入力してください。" #: src/iac_code/ui/repl.py:833 #, python-brace-format @@ -2138,9 +2198,7 @@ msgstr "(コマンドをクリップボードにコピーしました)" #: src/iac_code/ui/repl.py:1451 #, python-brace-format -msgid "" -"Current model {model} does not support image input. Use /model to switch " -"to a vision-capable model." +msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." msgstr "現在のモデル {model} は画像入力をサポートしていません。/model を使用してビジョン対応モデルに切り替えてください。" #: src/iac_code/ui/repl.py:1460 @@ -2149,9 +2207,7 @@ msgid "Image error: {err}" msgstr "画像エラー:{err}" #: src/iac_code/ui/repl.py:1477 -msgid "" -"Failed to persist image to cache; it will only exist in memory for this " -"turn." +msgid "Failed to persist image to cache; it will only exist in memory for this turn." msgstr "画像をキャッシュに保存できませんでした。このターンの間、メモリ上にのみ存在します。" #: src/iac_code/ui/spinner.py:52 @@ -2342,9 +2398,7 @@ msgstr "{total} 件中 {current}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "" -"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " -"cancel" +msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" msgstr "{count} 個のスキル - Space で切り替え、Enter で保存、Tab で並べ替え、Esc でキャンセル" #: src/iac_code/ui/dialogs/skills_picker.py:171 @@ -2431,9 +2485,7 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code を Windows で使用するには Git for Windows が必要です。" #: src/iac_code/utils/platform.py:41 -msgid "" -"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " -"variable." +msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." msgstr "インストール済みで PATH に含まれていない場合は、IAC_CODE_GIT_BASH_PATH 環境変数を設定してください。" #: src/iac_code/utils/platform.py:44 @@ -2445,9 +2497,7 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " 方法 1 - winget(github.com へのアクセスが必要):" #: src/iac_code/utils/platform.py:52 -msgid "" -" Option 2 - if you cannot reach github.com, run this to install via " -"npmmirror:" +msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" msgstr " オプション 2 - github.com にアクセスできない場合は、npmmirror 経由でインストールするために以下を実行してください:" #~ msgid "toggle preview" @@ -2471,13 +2521,8 @@ msgstr " オプション 2 - github.com にアクセスできない場合は、 #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "" -#~ "LLM provider is locked by '{source}'." -#~ " To change, modify llm_source in " -#~ "settings.yml." -#~ msgstr "" -#~ "LLM プロバイダーは '{source}' によりロックされています。変更するには " -#~ "settings.yml の llm_source を修正してください。" +#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." +#~ msgstr "LLM プロバイダーは '{source}' によりロックされています。変更するには settings.yml の llm_source を修正してください。" #~ msgid "Provider switched: {status}" #~ msgstr "Provider が切り替わりました: {status}" @@ -2494,9 +2539,6 @@ msgstr " オプション 2 - github.com にアクセスできない場合は、 #~ msgid "Cache create" #~ msgstr "キャッシュ作成" -#~ msgid "" -#~ "{count} skills - Space to toggle, " -#~ "Enter to save, / to search, t " -#~ "to sort, Esc to cancel" +#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" #~ msgstr "{count} 個のスキル - Space で切り替え、Enter で保存、/ で検索、t で並べ替え、Esc でキャンセル" 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 08b552b..0cbbac9 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:45+0800\n" +"POT-Creation-Date: 2026-06-02 21:26+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: pt\n" @@ -20,26 +20,16 @@ msgstr "" #: src/iac_code/config.py:164 #, python-brace-format msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}" -msgstr "" -"Valor inválido de IAC_CODE_PROVIDER: {!r}. Valores válidos (sem " -"diferenciar maiúsculas de minúsculas): {}" +msgstr "Valor inválido de IAC_CODE_PROVIDER: {!r}. Valores válidos (sem diferenciar maiúsculas de minúsculas): {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "" -"Unix domain socket transport is not supported on Windows. Use --transport" -" http or --transport stdio instead." -msgstr "" -"O transporte de socket de domínio Unix não é suportado no Windows. Use " -"--transport http ou --transport stdio." +msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." +msgstr "O transporte de socket de domínio Unix não é suportado no Windows. Use --transport http ou --transport stdio." #: src/iac_code/acp/slash_registry.py:44 #, python-brace-format -msgid "" -"Command '/{cmd_name}' is not supported over ACP. Supported commands: " -"{supported}" -msgstr "" -"O comando '/{cmd_name}' não é compatível com ACP. Comandos compatíveis: " -"{supported}" +msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" +msgstr "O comando '/{cmd_name}' não é compatível com ACP. Comandos compatíveis: {supported}" #: src/iac_code/acp/slash_registry.py:58 #, python-brace-format @@ -57,12 +47,10 @@ msgstr "Nada para compactar: a conversa está vazia." #: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "" -"Conversation too short to compact: all messages are within the recent " -"{turns}-turn preservation window." +msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." msgstr "" -"Conversa curta demais para compactar: todas as mensagens estão na janela " -"de preservação das últimas {turns} interações." +"Conversa curta demais para compactar: todas as mensagens estão na janela de preservação das últimas {turns} " +"interações." #: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." @@ -70,12 +58,8 @@ msgstr "Falha na compactação. Consulte os logs para detalhes." #: src/iac_code/acp/slash_registry.py:85 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent} reduction)." -" Context usage: {usage}" -msgstr "" -"Contexto compactado: {original} → {compacted} tokens (redução de " -"{percent}). Uso do contexto: {usage}" +msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" +msgstr "Contexto compactado: {original} → {compacted} tokens (redução de {percent}). Uso do contexto: {usage}" #: src/iac_code/acp/slash_registry.py:99 #, python-brace-format @@ -112,8 +96,8 @@ msgstr "Uso: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "O gerenciador de memória está indisponível." -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "Permissão negada." @@ -130,8 +114,7 @@ msgstr "Explorar" msgid "Plan" msgstr "Planejar" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 -#: src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "Agent" @@ -183,16 +166,14 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-" -"code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " Correção: execute iac-code e digite /auth\n" " ou defina a variável de ambiente: IAC_CODE_API_KEY=\n" -" Documentação: https://aliyun.github.io/iac-" -"code/pt/docs/configuration/authentication\n" +" Documentação: https://aliyun.github.io/iac-code/pt/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -205,9 +186,7 @@ msgstr "Instalando Git for Windows via npmmirror..." #: src/iac_code/cli/install_git_bash.py:59 msgid "powershell.exe was not found on PATH; cannot run installer." -msgstr "" -"powershell.exe não foi encontrado no PATH; não é possível executar o " -"instalador." +msgstr "powershell.exe não foi encontrado no PATH; não é possível executar o instalador." #: src/iac_code/cli/install_git_bash.py:66 #, python-brace-format @@ -216,11 +195,11 @@ msgstr "A instalação falhou (PowerShell saiu com código {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may " -"have been cancelled or the installer used a non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " +"non-standard path." msgstr "" -"O instalador encerrou mas bash.exe não foi encontrado em locais comuns; " -"UAC pode ter sido cancelado ou o instalador usou um caminho não padrão." +"O instalador encerrou mas bash.exe não foi encontrado em locais comuns; UAC pode ter sido cancelado ou o instalador " +"usou um caminho não padrão." #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -243,14 +222,9 @@ msgstr "Instalar Git for Windows pelo espelho npmmirror (somente Windows)." msgid "YAML config file containing A2A client options" msgstr "Arquivo de configuração YAML com opções do cliente A2A" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 -#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "" -"A2A client dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" -msgstr "" -"As dependências do cliente A2A estão ausentes. Instale com: pip install " -"'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgstr "As dependências do cliente A2A estão ausentes. Instale com: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 msgid "LLM model to use" @@ -268,8 +242,7 @@ msgstr "Formato de saída: text, json, stream-json" msgid "Maximum agent turns in headless mode" msgstr "Número máximo de passos do agent em modo headless" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 -#: src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "Ativar debug" @@ -294,20 +267,12 @@ msgid "Install completion for the current shell." msgstr "Instalar completion para o shell atual." #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "" -"Show completion for the current shell, to copy it or customize the " -"installation." -msgstr "" -"Exibir o completion do shell atual, para copiar ou personalizar a " -"instalação." +msgid "Show completion for the current shell, to copy it or customize the installation." +msgstr "Exibir o completion do shell atual, para copiar ou personalizar a instalação." #: src/iac_code/cli/main.py:110 -msgid "" -"Comma-separated tool permission patterns to allow, e.g. 'bash(git " -"*),write_file'" -msgstr "" -"Padrões de permissão de ferramentas a permitir (separados por vírgula), " -"ex. 'bash(git *),write_file'*),write_file'" +msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" +msgstr "Padrões de permissão de ferramentas a permitir (separados por vírgula), ex. 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 msgid "Comma-separated tool permission patterns to deny" @@ -351,45 +316,27 @@ msgid "YAML config file for A2A server options" msgstr "Arquivo de configuração YAML para opções do servidor A2A" #: src/iac_code/cli/main.py:584 -msgid "" -"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " -"not a registered A2A port." -msgstr "" -"Porta do servidor HTTP. 41242 é o padrão do iac-code inspirado no Gemini " -"CLI, não uma porta A2A registrada." +msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." +msgstr "Porta do servidor HTTP. 41242 é o padrão do iac-code inspirado no Gemini CLI, não uma porta A2A registrada." #: src/iac_code/cli/main.py:589 -msgid "" -"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " -"redis-streams" -msgstr "" -"Transporte A2A: http, stdio, unix, websocket, grpc, grpc-jsonrpc ou " -"redis-streams" +msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" +msgstr "Transporte A2A: http, stdio, unix, websocket, grpc, grpc-jsonrpc ou redis-streams" #: src/iac_code/cli/main.py:595 -msgid "" -"Expose A2A thinking signal types; repeat for multiple. Values: raw-" -"thinking, tool-trace." -msgstr "" -"Expõe tipos de sinal de thinking A2A; repita para múltiplos. Valores: " -"raw-thinking, tool-trace." +msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." +msgstr "Expõe tipos de sinal de thinking A2A; repita para múltiplos. Valores: raw-thinking, tool-trace." #: src/iac_code/cli/main.py:646 -msgid "" -"A2A server dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" -msgstr "" -"As dependências do servidor A2A estão ausentes. Instale com: pip install " -"'iac-code[a2a]'" +msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgstr "As dependências do servidor A2A estão ausentes. Instale com: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "Envia um prompt para um endpoint JSON-RPC A2A." -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 -#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 -#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "URL do endpoint JSON-RPC A2A" @@ -414,48 +361,33 @@ msgstr "Metadados do diretório de trabalho a enviar com a requisição" msgid "A2A context ID to continue" msgstr "ID de contexto A2A para continuar" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 -#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 -#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 -#: src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "Token Bearer para requisições HTTP A2A" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 -#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 -#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 -#: src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "Nome de usuário de autenticação básica para requisições HTTP A2A" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 -#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 -#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 -#: src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "Senha de autenticação básica para requisições HTTP A2A" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 -#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 -#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 -#: src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "Chave de API para requisições HTTP A2A" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 -#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 -#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 -#: src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "Nome do cabeçalho HTTP para a chave de API A2A" @@ -491,10 +423,8 @@ msgstr "URL base do agente A2A" msgid "Get an A2A task." msgstr "Obtém uma tarefa A2A." -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 -#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 -#: src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "ID da tarefa A2A" @@ -542,8 +472,7 @@ msgstr "Assina um fluxo de eventos de tarefa A2A." msgid "Create an A2A task push notification config." msgstr "Cria uma configuração de notificação push de tarefa A2A." -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 -#: src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "ID da configuração push" @@ -667,227 +596,295 @@ msgstr "Gerenciar habilidades" msgid "Show current session status" msgstr "Mostrar o status atual da sessão" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navegar" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:530 -#: src/iac_code/commands/auth.py:562 src/iac_code/commands/auth.py:569 -#: src/iac_code/commands/auth.py:604 src/iac_code/commands/auth.py:611 -#: src/iac_code/commands/auth.py:632 src/iac_code/commands/auth.py:1107 -#: src/iac_code/commands/auth.py:1319 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmar" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:528 -#: src/iac_code/commands/auth.py:530 src/iac_code/commands/auth.py:562 -#: src/iac_code/commands/auth.py:569 src/iac_code/commands/auth.py:604 -#: src/iac_code/commands/auth.py:611 src/iac_code/commands/auth.py:632 -#: src/iac_code/commands/auth.py:1107 src/iac_code/commands/auth.py:1217 -#: src/iac_code/commands/auth.py:1319 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "Voltar" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Keep" msgstr "Manter" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Re-enter" msgstr "Digitar novamente" -#: src/iac_code/commands/auth.py:741 src/iac_code/commands/auth.py:853 -#: src/iac_code/commands/auth.py:911 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:946 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (atual)" -#: src/iac_code/commands/auth.py:744 +#: src/iac_code/commands/auth.py:752 msgid "Custom model..." msgstr "Modelo personalizado..." -#: src/iac_code/commands/auth.py:747 +#: src/iac_code/commands/auth.py:755 #, python-brace-format msgid "Select model for {provider}" msgstr "Selecionar modelo para {provider}" -#: src/iac_code/commands/auth.py:749 +#: src/iac_code/commands/auth.py:757 msgid "Select model" msgstr "Selecionar modelo" -#: src/iac_code/commands/auth.py:757 +#: src/iac_code/commands/auth.py:765 msgid "Enter custom model name: " msgstr "Informe o nome do modelo personalizado: " -#: src/iac_code/commands/auth.py:783 +#: src/iac_code/commands/auth.py:791 msgid "Error: console not available" msgstr "Erro: console indisponível" -#: src/iac_code/commands/auth.py:810 +#: src/iac_code/commands/auth.py:818 msgid "Configure LLM Provider" msgstr "Configurar provedor LLM" -#: src/iac_code/commands/auth.py:811 +#: src/iac_code/commands/auth.py:819 msgid "Configure IaC Cloud Service" msgstr "Configurar serviço de nuvem IaC" -#: src/iac_code/commands/auth.py:813 +#: src/iac_code/commands/auth.py:821 msgid "Select configuration type" msgstr "Selecionar tipo de configuração" -#: src/iac_code/commands/auth.py:815 src/iac_code/commands/auth.py:971 -#: src/iac_code/commands/auth.py:987 src/iac_code/commands/auth.py:1066 -#: src/iac_code/commands/auth.py:1258 src/iac_code/commands/auth.py:1299 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 +#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "Autenticação cancelada" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:951 -#: src/iac_code/commands/auth.py:1041 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "Selecionar provedor — {group}" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:909 -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "Terceiros" -#: src/iac_code/commands/auth.py:867 +#: src/iac_code/commands/auth.py:875 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:868 src/iac_code/commands/auth.py:1019 +#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 msgid "Configured" msgstr "Configurado" -#: src/iac_code/commands/auth.py:923 +#: src/iac_code/commands/auth.py:931 msgid "Select provider" msgstr "Selecionar provedor" -#: src/iac_code/commands/auth.py:964 +#: src/iac_code/commands/auth.py:972 #, python-brace-format msgid "Configure {provider}" msgstr "Configurar {provider}" -#: src/iac_code/commands/auth.py:980 +#: src/iac_code/commands/auth.py:988 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Informe a API key para {provider}" -#: src/iac_code/commands/auth.py:1018 +#: src/iac_code/commands/auth.py:1026 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:1027 src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1028 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1029 +#: src/iac_code/commands/auth.py:1037 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1030 +#: src/iac_code/commands/auth.py:1038 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1031 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1032 +#: src/iac_code/commands/auth.py:1040 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1033 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1034 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1035 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1037 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1039 +#: src/iac_code/commands/auth.py:1047 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1048 msgid "Compatible" msgstr "Compatível" -#: src/iac_code/commands/auth.py:1057 +#: src/iac_code/commands/auth.py:1065 msgid "Select Cloud Provider" msgstr "Selecionar provedor de nuvem" -#: src/iac_code/commands/auth.py:1073 +#: src/iac_code/commands/auth.py:1081 msgid "Credential" msgstr "Credencial" -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 -#: src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 +#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Região" -#: src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1084 msgid "Configure Alibaba Cloud" msgstr "Configurar Alibaba Cloud" -#: src/iac_code/commands/auth.py:1178 +#: src/iac_code/commands/auth.py:1186 msgid "Current configuration" msgstr "Configuração atual" -#: src/iac_code/commands/auth.py:1180 +#: src/iac_code/commands/auth.py:1188 msgid "Mode" msgstr "Modo" -#: src/iac_code/commands/auth.py:1187 +#: src/iac_code/commands/auth.py:1194 msgid "(not set)" msgstr "(não definido)" -#: src/iac_code/commands/auth.py:1204 +#: src/iac_code/commands/auth.py:1203 +msgid "AccessKey" +msgstr "AccessKey" + +#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +msgid "STS Token" +msgstr "Token STS" + +#: src/iac_code/commands/auth.py:1207 +msgid "RAM Role" +msgstr "Função RAM" + +#: src/iac_code/commands/auth.py:1209 +msgid "OAuth Login (Browser)" +msgstr "Login OAuth (navegador)" + +#: src/iac_code/commands/auth.py:1215 +msgid "AccessKey ID" +msgstr "ID da AccessKey" + +#: src/iac_code/commands/auth.py:1216 +msgid "AccessKey Secret" +msgstr "Segredo da AccessKey" + +#: src/iac_code/commands/auth.py:1218 +msgid "RAM Role ARN" +msgstr "ARN da função RAM" + +#: src/iac_code/commands/auth.py:1219 +msgid "Session Name" +msgstr "Nome da sessão" + +#: src/iac_code/commands/auth.py:1220 +msgid "OAuth Site Type" +msgstr "Tipo de site OAuth" + +#: src/iac_code/commands/auth.py:1221 +msgid "OAuth Access Token" +msgstr "Token de acesso OAuth" + +#: src/iac_code/commands/auth.py:1222 +msgid "OAuth Refresh Token" +msgstr "Token de atualização OAuth" + +#: src/iac_code/commands/auth.py:1223 +msgid "OAuth Access Token Expire" +msgstr "Expiração do token de acesso OAuth" + +#: src/iac_code/commands/auth.py:1224 +msgid "OAuth Refresh Token Expire" +msgstr "Expiração do token de atualização OAuth" + +#: src/iac_code/commands/auth.py:1225 +msgid "STS Expiration" +msgstr "Expiração STS" + +#: src/iac_code/commands/auth.py:1382 +msgid "China" +msgstr "China" + +#: src/iac_code/commands/auth.py:1383 +msgid "International" +msgstr "Internacional" + +#: src/iac_code/commands/auth.py:1385 +msgid "Choose site type" +msgstr "Escolha o tipo de site" + +#: src/iac_code/commands/auth.py:1400 +#, python-brace-format +msgid "Alibaba Cloud OAuth login failed: {error}" +msgstr "Falha no login OAuth da Alibaba Cloud: {error}" + +#: src/iac_code/commands/auth.py:1416 +msgid "Configured: Alibaba Cloud OAuth credentials saved" +msgstr "Configurado: credenciais OAuth da Alibaba Cloud salvas" + +#: src/iac_code/commands/auth.py:1428 msgid "Configure Alibaba Cloud credentials" msgstr "Configurar credenciais da Alibaba Cloud" -#: src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1441 msgid "Reconfigure credential" msgstr "Reconfigurar credencial" -#: src/iac_code/commands/auth.py:1230 +#: src/iac_code/commands/auth.py:1454 msgid "Select credential type" msgstr "Selecionar tipo de credencial" -#: src/iac_code/commands/auth.py:1280 +#: src/iac_code/commands/auth.py:1510 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:1287 +#: src/iac_code/commands/auth.py:1517 msgid "Configure Alibaba Cloud region" msgstr "Configurar região da Alibaba Cloud" -#: src/iac_code/commands/auth.py:1313 +#: src/iac_code/commands/auth.py:1543 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Configurado: região da Alibaba Cloud salva em ~/.iac-code" @@ -905,12 +902,8 @@ msgstr "Nenhum agent loop ativo." #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent_display} " -"reduction). Context usage: {usage_display}" -msgstr "" -"Contexto compactado: {original} → {compacted} tokens (redução de " -"{percent_display}). Uso do contexto: {usage_display}" +msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" +msgstr "Contexto compactado: {original} → {compacted} tokens (redução de {percent_display}). Uso do contexto: {usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -920,8 +913,7 @@ msgstr "O comando debug requer um contexto." msgid "No active session." msgstr "Nenhuma sessão ativa." -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 -#: src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "Nenhum provedor configurado. Execute /auth primeiro." @@ -1015,12 +1007,8 @@ msgstr "Memória '{name}' excluída." #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "" -"Model is managed by '{source}'. To change model, modify it in {source} or" -" switch provider via /auth." -msgstr "" -"O modelo é gerenciado por '{source}'. Para alterá-lo, modifique em " -"{source} ou troque de provedor via /auth." +msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." +msgstr "O modelo é gerenciado por '{source}'. Para alterá-lo, modifique em {source} ou troque de provedor via /auth." #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1086,8 +1074,7 @@ msgstr "Sessão" msgid "Provider" msgstr "Provedor" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 -#: src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 msgid "not configured" msgstr "não configurado" @@ -1188,10 +1175,8 @@ msgstr "Abortado!" #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." msgstr "" -"Cannot determine provider for model: {model}. Run /auth to " -"configure.Cannot determine provider for model: {model}. Run /auth to " -"configure.Não é possível determinar o provedor para o modelo: {model}. " -"Execute /auth para configurar." +"Cannot determine provider for model: {model}. Run /auth to configure.Cannot determine provider for model: {model}. " +"Run /auth to configure.Não é possível determinar o provedor para o modelo: {model}. Execute /auth para configurar." #: src/iac_code/providers/manager.py:95 #, python-brace-format @@ -1200,34 +1185,26 @@ msgstr "Chave de provedor desconhecida: '{key}'. Execute /auth para configurar." #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "" -"No API key configured for provider '{provider}' (model: {model}). Run " -"/auth to configure." -msgstr "" -"Nenhuma chave API configurada para o provedor '{provider}' (modelo: " -"{model}). Execute /auth para configurar." +msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." +msgstr "Nenhuma chave API configurada para o provedor '{provider}' (modelo: {model}). Execute /auth para configurar." #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct " -"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " -"suffix (e.g. {base_url}/v1)." +"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " +"endpoints require a /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"A API não retornou dados. Verifique se a API Base URL está correta " -"(atual: {base_url}). Muitos endpoints compatíveis com OpenAI exigem o " -"sufixo /v1 (por exemplo, {base_url}/v1)." +"A API não retornou dados. Verifique se a API Base URL está correta (atual: {base_url}). Muitos endpoints compatíveis " +"com OpenAI exigem o sufixo /v1 (por exemplo, {base_url}/v1)." #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is " -"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" -" /v1 suffix (e.g. {base_url}/v1)." +"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" +"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"A API retornou uma resposta inválida. Verifique se a API Base URL está " -"correta (atual: {base_url}). Muitos endpoints compatíveis com OpenAI " -"exigem o sufixo /v1 (por exemplo, {base_url}/v1)." +"A API retornou uma resposta inválida. Verifique se a API Base URL está correta (atual: {base_url}). Muitos endpoints " +"compatíveis com OpenAI exigem o sufixo /v1 (por exemplo, {base_url}/v1)." #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1308,29 +1285,127 @@ msgstr "Compatível com Anthropic" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " -"support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " -"mode (remove 'llm_source: qwenpaw' from settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " +"settings.yml)." msgstr "" -"[Modo QwenPaw] Provider desconhecido '{provider_id}'. iac-code não " -"suporta este provider.\n" +"[Modo QwenPaw] Provider desconhecido '{provider_id}'. iac-code não suporta este provider.\n" "IDs de providers QwenPaw suportados: {supported_ids}\n" -"Solução: mude para um provider suportado no QwenPaw, ou desative o modo " -"QwenPaw (remova 'llm_source: qwenpaw' do settings.yml)." +"Solução: mude para um provider suportado no QwenPaw, ou desative o modo QwenPaw (remova 'llm_source: qwenpaw' do " +"settings.yml)." #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "--permission-mode inválido {!r}. Valores válidos: {}" -#: src/iac_code/services/permissions/pipeline.py:54 -#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "Permitir {}?" +#: src/iac_code/services/providers/aliyun.py:144 +msgid "Alibaba Cloud OAuth site is missing." +msgstr "O site OAuth da Alibaba Cloud está ausente." + +#: src/iac_code/services/providers/aliyun.py:150 +msgid "Alibaba Cloud OAuth refresh token is missing." +msgstr "O token de atualização OAuth da Alibaba Cloud está ausente." + +#: src/iac_code/services/providers/aliyun.py:158 +msgid "Alibaba Cloud OAuth access token is missing." +msgstr "O token de acesso OAuth da Alibaba Cloud está ausente." + +#: src/iac_code/services/providers/aliyun_oauth.py:83 +msgid "Run /auth and choose OAuth Login (Browser)." +msgstr "Execute /auth e escolha Login OAuth (navegador)." + +#: src/iac_code/services/providers/aliyun_oauth.py:106 +#, python-brace-format +msgid "Unknown Aliyun OAuth site: {site_type}" +msgstr "Site OAuth Aliyun desconhecido: {site_type}" + +#: src/iac_code/services/providers/aliyun_oauth.py:164 +msgid "Not found" +msgstr "Não encontrado" + +#: src/iac_code/services/providers/aliyun_oauth.py:170 +msgid "invalid state" +msgstr "estado inválido" + +#: src/iac_code/services/providers/aliyun_oauth.py:171 +msgid "Invalid state" +msgstr "Estado inválido" + +#: src/iac_code/services/providers/aliyun_oauth.py:176 +msgid "code not found" +msgstr "código de autorização não encontrado" + +#: src/iac_code/services/providers/aliyun_oauth.py:177 +msgid "Authorization code not found" +msgstr "Código de autorização não encontrado" + +#: src/iac_code/services/providers/aliyun_oauth.py:181 +msgid "Authorization successful. You can close this window." +msgstr "Autorização bem-sucedida. Você pode fechar esta janela." + +#: src/iac_code/services/providers/aliyun_oauth.py:212 +#, python-brace-format +msgid "No available callback port in range {start}-{end}" +msgstr "Nenhuma porta de callback disponível no intervalo {start}-{end}" + +#: src/iac_code/services/providers/aliyun_oauth.py:227 +msgid "OAuth login cancelled." +msgstr "Login OAuth cancelado." + +#: src/iac_code/services/providers/aliyun_oauth.py:278 +msgid "Open in your browser:" +msgstr "Abrir no navegador:" + +#: src/iac_code/services/providers/aliyun_oauth.py:299 +msgid "Waiting for browser authorization" +msgstr "Aguardando autorização do navegador" + +#: src/iac_code/services/providers/aliyun_oauth.py:300 +msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." +msgstr "1. O navegador pode mostrar official-cli; este é o aplicativo OAuth oficial da CLI da Alibaba Cloud." + +#: src/iac_code/services/providers/aliyun_oauth.py:302 +msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgstr "" +"2. Se a atribuição for necessária, atribua o usuário RAM ou a função RAM que está conectada. Grupos de usuários não " +"são compatíveis." + +#: src/iac_code/services/providers/aliyun_oauth.py:306 +msgid "" +"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " +"out of Alibaba Cloud and sign in again." +msgstr "" +"3. Após a atribuição, feche a página de autorização antiga e execute Login OAuth (navegador) novamente. Se ainda " +"falhar, saia da Alibaba Cloud e entre novamente." + +#: src/iac_code/services/providers/aliyun_oauth.py:310 +msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgstr "" +"4. As credenciais STS são atualizadas quando possível até expirarem na Alibaba Cloud. Se a atualização falhar, " +"execute /auth novamente." + +#: src/iac_code/services/providers/aliyun_oauth.py:313 +msgid "Press Esc to cancel while waiting." +msgstr "Pressione Esc para cancelar a espera." + +#: src/iac_code/services/providers/aliyun_oauth.py:321 +msgid "" +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" +" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " +"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +msgstr "" +"Tempo esgotado aguardando o callback OAuth. Se a Alibaba Cloud pediu para atribuir o aplicativo official-cli, " +"atribua-o ao usuário RAM ou à função RAM exata atualmente conectada. Grupos de usuários não são compatíveis. Depois " +"feche a página de autorização antiga, saia da Alibaba Cloud e entre novamente se necessário, e execute /auth para " +"escolher Login OAuth (navegador) novamente." + #: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." @@ -1351,12 +1426,10 @@ msgid "Skill disabled: {name}" msgstr "Habilidade desativada: {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "" -"Review changed code for reuse, quality, and efficiency, then fix issues " -"found." +msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." msgstr "" -"Revise o código alterado quanto à reutilização, qualidade e eficiência e," -" em seguida, corrija os problemas encontrados." +"Revise o código alterado quanto à reutilização, qualidade e eficiência e, em seguida, corrija os problemas " +"encontrados." #: src/iac_code/tools/edit_file.py:116 msgid "Edit" @@ -1454,8 +1527,8 @@ msgstr "A URL não pode estar vazia." #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}URL " -"inválida: esquema ausente (ex.: http:// ou https://). Recebido: {url}" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}URL inválida: esquema ausente (ex.: http:// ou " +"https://). Recebido: {url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1564,8 +1637,7 @@ msgstr "Regra(s) de negação correspondente(s): {}" msgid "matched ask rule(s): {}" msgstr "Regra(s) de consulta correspondente(s): {}" -#: src/iac_code/tools/bash/permissions.py:154 -#: src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "Regra(s) de permissão correspondente(s): {}" @@ -1622,8 +1694,7 @@ msgstr "API na nuvem" msgid "Calling {action}..." msgstr "Chamando {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 -#: src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Chamada bem-sucedida" @@ -1636,8 +1707,7 @@ msgstr "Resposta recebida ({count} linhas)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 -#: src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "Executando {action}..." @@ -1782,11 +1852,11 @@ msgstr "Importação concluída" msgid "IMPORT_FAILED" msgstr "Falha na importação" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:171 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:172 msgid "Aliyun API" msgstr "Aliyun API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "Chamada bem-sucedida (RequestId: {request_id})" @@ -1843,8 +1913,8 @@ msgstr "{count} documentos encontrados (total {total})" #: src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py:143 msgid "Use web_fetch tool to read full document content if needed." msgstr "" -"Use web_fetch tool to read full document content if needed.Se necessário," -" use a ferramenta web_fetch para ler o conteúdo completo." +"Use web_fetch tool to read full document content if needed.Se necessário, use a ferramenta web_fetch para ler o " +"conteúdo completo." #: src/iac_code/tools/cloud/aliyun/ros_stack.py:143 msgid "ROS Stack" @@ -1877,20 +1947,12 @@ msgstr "Erro de sintaxe JSON no modelo (linha {line}, coluna {col}): {msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "" -"Template {fmt} parse result is not an object (dict), please check the " -"template format" -msgstr "" -"O resultado da análise {fmt} do modelo não é um objeto (dict), por favor " -"verifique o formato do modelo" +msgid "Template {fmt} parse result is not an object (dict), please check the template format" +msgstr "O resultado da análise {fmt} do modelo não é um objeto (dict), por favor verifique o formato do modelo" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "" -"Template is missing ROSTemplateFormatVersion (ROS templates must include " -"this field, e.g. '2015-09-01')" -msgstr "" -"O modelo não contém ROSTemplateFormatVersion (modelos ROS devem incluir " -"este campo, ex. '2015-09-01')" +msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" +msgstr "O modelo não contém ROSTemplateFormatVersion (modelos ROS devem incluir este campo, ex. '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" @@ -1903,12 +1965,8 @@ msgstr "Resources deve ser um objeto (dict), o tipo atual é {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "" -"Resource '{name}' definition must be an object (dict), current type is " -"{type}" -msgstr "" -"A definição do recurso '{name}' deve ser um objeto (dict), o tipo atual é" -" {type}" +msgid "Resource '{name}' definition must be an object (dict), current type is {type}" +msgstr "A definição do recurso '{name}' deve ser um objeto (dict), o tipo atual é {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 #, python-brace-format @@ -1918,17 +1976,11 @@ msgstr "O recurso '{name}' não possui o campo Type" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:129 #, python-brace-format msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" -msgstr "" -"O recurso '{name}' possui o tipo incorreto '{wrong}', deveria ser " -"'{correct}'" +msgstr "O recurso '{name}' possui o tipo incorreto '{wrong}', deveria ser '{correct}'" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "" -"Template structure validation found the following issues, please fix and " -"retry:" -msgstr "" -"A validação da estrutura do modelo encontrou os seguintes problemas, por " -"favor corrija e tente novamente:" +msgid "Template structure validation found the following issues, please fix and retry:" +msgstr "A validação da estrutura do modelo encontrou os seguintes problemas, por favor corrija e tente novamente:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 #, python-brace-format @@ -1964,14 +2016,12 @@ msgstr "Modo debug" msgid "Log file" msgstr "Arquivo de log" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 -#: src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "Raciocínio por {seconds:.1f}s" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 -#: src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o para expandir)" @@ -2079,9 +2129,7 @@ msgstr "Atualizar agora" #: src/iac_code/ui/repl.py:496 msgid "Run the shown update command and exit when it succeeds." -msgstr "" -"Executa o comando de atualização mostrado e sai quando ele for concluído " -"com sucesso." +msgstr "Executa o comando de atualização mostrado e sai quando ele for concluído com sucesso." #: src/iac_code/ui/repl.py:499 msgid "Skip" @@ -2122,9 +2170,7 @@ msgstr "O suporte a comandos shell não está disponível." #: src/iac_code/ui/repl.py:826 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." -msgstr "" -"Habilidade desconhecida: ${name}. Digite / para listar comandos e " -"habilidades." +msgstr "Habilidade desconhecida: ${name}. Digite / para listar comandos e habilidades." #: src/iac_code/ui/repl.py:828 #, python-brace-format @@ -2176,12 +2222,8 @@ msgstr "(Comando copiado para a área de transferência)" #: src/iac_code/ui/repl.py:1451 #, python-brace-format -msgid "" -"Current model {model} does not support image input. Use /model to switch " -"to a vision-capable model." -msgstr "" -"O modelo atual {model} não suporta entrada de imagem. Use /model para " -"alternar para um modelo com capacidade de visão." +msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." +msgstr "O modelo atual {model} não suporta entrada de imagem. Use /model para alternar para um modelo com capacidade de visão." #: src/iac_code/ui/repl.py:1460 #, python-brace-format @@ -2189,12 +2231,8 @@ msgid "Image error: {err}" msgstr "Erro de imagem: {err}" #: src/iac_code/ui/repl.py:1477 -msgid "" -"Failed to persist image to cache; it will only exist in memory for this " -"turn." -msgstr "" -"Falha ao persistir a imagem no cache; ela só existirá na memória durante " -"este turno." +msgid "Failed to persist image to cache; it will only exist in memory for this turn." +msgstr "Falha ao persistir a imagem no cache; ela só existirá na memória durante este turno." #: src/iac_code/ui/spinner.py:52 msgid "Processing" @@ -2384,12 +2422,8 @@ msgstr "{current} de {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "" -"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " -"cancel" -msgstr "" -"{count} habilidades - Espaço para alternar, Enter para salvar, Tab para " -"ordenar, Esc para cancelar" +msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" +msgstr "{count} habilidades - Espaço para alternar, Enter para salvar, Tab para ordenar, Esc para cancelar" #: src/iac_code/ui/dialogs/skills_picker.py:171 #, python-brace-format @@ -2475,12 +2509,8 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code no Windows requer o Git for Windows." #: src/iac_code/utils/platform.py:41 -msgid "" -"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " -"variable." -msgstr "" -"Se estiver instalado mas não estiver no PATH, defina a variável de " -"ambiente IAC_CODE_GIT_BASH_PATH." +msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." +msgstr "Se estiver instalado mas não estiver no PATH, defina a variável de ambiente IAC_CODE_GIT_BASH_PATH." #: src/iac_code/utils/platform.py:44 msgid "To install:" @@ -2491,12 +2521,8 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " Opção 1 - winget (requer acesso a github.com):" #: src/iac_code/utils/platform.py:52 -msgid "" -" Option 2 - if you cannot reach github.com, run this to install via " -"npmmirror:" -msgstr "" -" Opção 2 - se você não consegue acessar github.com, execute isto para " -"instalar via npmmirror:" +msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" +msgstr " Opção 2 - se você não consegue acessar github.com, execute isto para instalar via npmmirror:" #~ msgid "DashScope Token Plan" #~ msgstr "DashScope Token Plan" @@ -2504,14 +2530,8 @@ msgstr "" #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "" -#~ "LLM provider is locked by '{source}'." -#~ " To change, modify llm_source in " -#~ "settings.yml." -#~ msgstr "" -#~ "O provedor LLM está bloqueado por " -#~ "'{source}'. Para alterar, modifique llm_source" -#~ " em settings.yml." +#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." +#~ msgstr "O provedor LLM está bloqueado por '{source}'. Para alterar, modifique llm_source em settings.yml." #~ msgid "Provider switched: {status}" #~ msgstr "Provider alterado: {status}" @@ -2528,13 +2548,6 @@ msgstr "" #~ msgid "Cache create" #~ msgstr "Criação de cache" -#~ msgid "" -#~ "{count} skills - Space to toggle, " -#~ "Enter to save, / to search, t " -#~ "to sort, Esc to cancel" -#~ msgstr "" -#~ "{count} habilidades - Espaço para " -#~ "alternar, Enter para salvar, / para " -#~ "buscar, t para ordenar, Esc para " -#~ "cancelar" +#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" +#~ msgstr "{count} habilidades - Espaço para alternar, Enter para salvar, / para buscar, t para ordenar, Esc para cancelar" 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 a9fec79..9826a09 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 20:45+0800\n" +"POT-Creation-Date: 2026-06-02 21:26+0800\n" "PO-Revision-Date: 2026-04-02 00:00+0000\n" "Last-Translator: \n" "Language: zh\n" @@ -23,16 +23,12 @@ msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): { msgstr "无效的 IAC_CODE_PROVIDER 值:{!r}。有效值(不区分大小写):{}" #: src/iac_code/a2a/transports/base.py:175 -msgid "" -"Unix domain socket transport is not supported on Windows. Use --transport" -" http or --transport stdio instead." +msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." msgstr "Windows 不支持 Unix 域套接字传输。请使用 --transport http 或 --transport stdio。" #: src/iac_code/acp/slash_registry.py:44 #, python-brace-format -msgid "" -"Command '/{cmd_name}' is not supported over ACP. Supported commands: " -"{supported}" +msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" msgstr "命令 '/{cmd_name}' 不支持通过 ACP 使用。支持的命令:{supported}" #: src/iac_code/acp/slash_registry.py:58 @@ -51,9 +47,7 @@ msgstr "无内容可压缩:对话为空。" #: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "" -"Conversation too short to compact: all messages are within the recent " -"{turns}-turn preservation window." +msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." msgstr "对话过短,无需压缩:所有消息都在最近 {turns} 轮保留窗口内。" #: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 @@ -62,9 +56,7 @@ msgstr "压缩失败,详情请查看日志。" #: src/iac_code/acp/slash_registry.py:85 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent} reduction)." -" Context usage: {usage}" +msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" msgstr "上下文已压缩:{original} → {compacted} tokens(减少 {percent})。上下文用量:{usage}" #: src/iac_code/acp/slash_registry.py:99 @@ -102,8 +94,8 @@ msgstr "用法:/debug [on|off]" msgid "Memory manager is unavailable." msgstr "记忆管理器不可用。" -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:793 src/iac_code/ui/repl.py:807 +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 +#: src/iac_code/ui/repl.py:807 msgid "Permission denied." msgstr "权限被拒绝。" @@ -120,8 +112,7 @@ msgstr "探索" msgid "Plan" msgstr "规划" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 -#: src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "智能体" @@ -173,16 +164,14 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-" -"code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " 修复方法:运行 iac-code 后输入 /auth\n" " 或者设置环境变量:IAC_CODE_API_KEY=<你的密钥>\n" -" 文档:https://aliyun.github.io/iac-code/zh-" -"Hans/docs/configuration/authentication\n" +" 文档:https://aliyun.github.io/iac-code/zh-Hans/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -204,8 +193,8 @@ msgstr "安装失败(PowerShell 退出码 {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may " -"have been cancelled or the installer used a non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " +"non-standard path." msgstr "安装程序已退出,但常见路径下未找到 bash.exe;可能 UAC 被取消或安装程序使用了非标准路径。" #: src/iac_code/cli/install_git_bash.py:84 @@ -229,11 +218,8 @@ msgstr "通过 npmmirror 镜像安装 Git for Windows(仅 Windows)。" msgid "YAML config file containing A2A client options" msgstr "包含 A2A 客户端选项的 YAML 配置文件" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 -#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "" -"A2A client dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" msgstr "缺少 A2A 客户端依赖。请使用以下命令安装:pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 @@ -252,8 +238,7 @@ msgstr "输出格式:text、json、stream-json" msgid "Maximum agent turns in headless mode" msgstr "无头模式下最大智能体轮次" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 -#: src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "启用调试日志" @@ -278,15 +263,11 @@ msgid "Install completion for the current shell." msgstr "为当前 shell 安装自动补全。" #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "" -"Show completion for the current shell, to copy it or customize the " -"installation." +msgid "Show completion for the current shell, to copy it or customize the installation." msgstr "显示当前 shell 的自动补全脚本,可复制或自定义安装。" #: src/iac_code/cli/main.py:110 -msgid "" -"Comma-separated tool permission patterns to allow, e.g. 'bash(git " -"*),write_file'" +msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" msgstr "允许的工具权限模式(逗号分隔),例如 'bash(git *),write_file'" #: src/iac_code/cli/main.py:115 @@ -331,37 +312,27 @@ msgid "YAML config file for A2A server options" msgstr "用于 A2A 服务器选项的 YAML 配置文件" #: src/iac_code/cli/main.py:584 -msgid "" -"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " -"not a registered A2A port." +msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." msgstr "HTTP 服务器端口。41242 是受 Gemini CLI 启发的 iac-code 默认值,并非已注册的 A2A 端口。" #: src/iac_code/cli/main.py:589 -msgid "" -"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " -"redis-streams" +msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" msgstr "A2A 传输方式:http、stdio、unix、websocket、grpc、grpc-jsonrpc 或 redis-streams" #: src/iac_code/cli/main.py:595 -msgid "" -"Expose A2A thinking signal types; repeat for multiple. Values: raw-" -"thinking, tool-trace." +msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." msgstr "暴露 A2A thinking 信号类型;可重复指定多个。取值:raw-thinking、tool-trace。" #: src/iac_code/cli/main.py:646 -msgid "" -"A2A server dependencies are missing. Install with: pip install 'iac-" -"code[a2a]'" +msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" msgstr "缺少 A2A 服务器依赖。请使用以下命令安装:pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "向 A2A JSON-RPC 端点发送提示。" -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 -#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 -#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "A2A JSON-RPC 端点 URL" @@ -386,48 +357,33 @@ msgstr "随请求一起发送的工作目录元数据" msgid "A2A context ID to continue" msgstr "要继续的 A2A 上下文 ID" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 -#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 -#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 -#: src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "用于 A2A HTTP 请求的 Bearer 令牌" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 -#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 -#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 -#: src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "用于 A2A HTTP 请求的基本认证用户名" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 -#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 -#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 -#: src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "用于 A2A HTTP 请求的基本认证密码" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 -#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 -#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 -#: src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "用于 A2A HTTP 请求的 API 密钥" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 -#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 -#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 -#: src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "A2A API 密钥使用的 HTTP 请求头名称" @@ -463,10 +419,8 @@ msgstr "A2A 代理基础 URL" msgid "Get an A2A task." msgstr "获取 A2A 任务。" -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 -#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 -#: src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "A2A 任务 ID" @@ -514,8 +468,7 @@ msgstr "订阅 A2A 任务事件流。" msgid "Create an A2A task push notification config." msgstr "创建 A2A 任务推送通知配置。" -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 -#: src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "推送配置 ID" @@ -639,227 +592,295 @@ msgstr "管理技能" msgid "Show current session status" msgstr "显示当前会话状态" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:1107 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "导航" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:530 -#: src/iac_code/commands/auth.py:562 src/iac_code/commands/auth.py:569 -#: src/iac_code/commands/auth.py:604 src/iac_code/commands/auth.py:611 -#: src/iac_code/commands/auth.py:632 src/iac_code/commands/auth.py:1107 -#: src/iac_code/commands/auth.py:1319 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "确认" -#: src/iac_code/commands/auth.py:382 src/iac_code/commands/auth.py:528 -#: src/iac_code/commands/auth.py:530 src/iac_code/commands/auth.py:562 -#: src/iac_code/commands/auth.py:569 src/iac_code/commands/auth.py:604 -#: src/iac_code/commands/auth.py:611 src/iac_code/commands/auth.py:632 -#: src/iac_code/commands/auth.py:1107 src/iac_code/commands/auth.py:1217 -#: src/iac_code/commands/auth.py:1319 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "返回" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Keep" msgstr "保留" -#: src/iac_code/commands/auth.py:528 +#: src/iac_code/commands/auth.py:536 msgid "Re-enter" msgstr "重新输入" -#: src/iac_code/commands/auth.py:741 src/iac_code/commands/auth.py:853 -#: src/iac_code/commands/auth.py:911 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:946 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (当前)" -#: src/iac_code/commands/auth.py:744 +#: src/iac_code/commands/auth.py:752 msgid "Custom model..." msgstr "自定义模型..." -#: src/iac_code/commands/auth.py:747 +#: src/iac_code/commands/auth.py:755 #, python-brace-format msgid "Select model for {provider}" msgstr "为 {provider} 选择模型" -#: src/iac_code/commands/auth.py:749 +#: src/iac_code/commands/auth.py:757 msgid "Select model" msgstr "选择模型" -#: src/iac_code/commands/auth.py:757 +#: src/iac_code/commands/auth.py:765 msgid "Enter custom model name: " msgstr "输入自定义模型名称:" -#: src/iac_code/commands/auth.py:783 +#: src/iac_code/commands/auth.py:791 msgid "Error: console not available" msgstr "错误:控制台不可用" -#: src/iac_code/commands/auth.py:810 +#: src/iac_code/commands/auth.py:818 msgid "Configure LLM Provider" msgstr "配置 LLM 提供商" -#: src/iac_code/commands/auth.py:811 +#: src/iac_code/commands/auth.py:819 msgid "Configure IaC Cloud Service" msgstr "配置 IaC 云服务" -#: src/iac_code/commands/auth.py:813 +#: src/iac_code/commands/auth.py:821 msgid "Select configuration type" msgstr "选择配置类型" -#: src/iac_code/commands/auth.py:815 src/iac_code/commands/auth.py:971 -#: src/iac_code/commands/auth.py:987 src/iac_code/commands/auth.py:1066 -#: src/iac_code/commands/auth.py:1258 src/iac_code/commands/auth.py:1299 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 +#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "认证已取消" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:951 -#: src/iac_code/commands/auth.py:1041 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "选择提供商 — {group}" -#: src/iac_code/commands/auth.py:857 src/iac_code/commands/auth.py:909 -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "第三方" -#: src/iac_code/commands/auth.py:867 +#: src/iac_code/commands/auth.py:875 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}:{provider}" -#: src/iac_code/commands/auth.py:868 src/iac_code/commands/auth.py:1019 +#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 msgid "Configured" msgstr "已配置" -#: src/iac_code/commands/auth.py:923 +#: src/iac_code/commands/auth.py:931 msgid "Select provider" msgstr "选择提供商" -#: src/iac_code/commands/auth.py:964 +#: src/iac_code/commands/auth.py:972 #, python-brace-format msgid "Configure {provider}" msgstr "配置 {provider}" -#: src/iac_code/commands/auth.py:980 +#: src/iac_code/commands/auth.py:988 #, python-brace-format msgid "Enter API key for {provider}" msgstr "为 {provider} 输入 API 密钥" -#: src/iac_code/commands/auth.py:1018 +#: src/iac_code/commands/auth.py:1026 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}:{provider} / {model}" -#: src/iac_code/commands/auth.py:1027 src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 msgid "Alibaba Cloud" msgstr "阿里云" -#: src/iac_code/commands/auth.py:1028 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 msgid "ZhiPu AI" msgstr "智谱 AI" -#: src/iac_code/commands/auth.py:1029 +#: src/iac_code/commands/auth.py:1037 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1030 +#: src/iac_code/commands/auth.py:1038 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1031 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 msgid "Volcengine" msgstr "火山引擎" -#: src/iac_code/commands/auth.py:1032 +#: src/iac_code/commands/auth.py:1040 msgid "SiliconFlow" msgstr "硅基流动" -#: src/iac_code/commands/auth.py:1033 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1034 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1035 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1037 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1039 +#: src/iac_code/commands/auth.py:1047 msgid "Local" msgstr "本地模型" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1048 msgid "Compatible" msgstr "兼容模式" -#: src/iac_code/commands/auth.py:1057 +#: src/iac_code/commands/auth.py:1065 msgid "Select Cloud Provider" msgstr "选择云服务商" -#: src/iac_code/commands/auth.py:1073 +#: src/iac_code/commands/auth.py:1081 msgid "Credential" msgstr "凭证" -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1190 -#: src/iac_code/commands/auth.py:1295 src/iac_code/commands/status.py:36 -#: src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 +#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "地域" -#: src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1084 msgid "Configure Alibaba Cloud" msgstr "配置阿里云" -#: src/iac_code/commands/auth.py:1178 +#: src/iac_code/commands/auth.py:1186 msgid "Current configuration" msgstr "当前配置" -#: src/iac_code/commands/auth.py:1180 +#: src/iac_code/commands/auth.py:1188 msgid "Mode" msgstr "模式" -#: src/iac_code/commands/auth.py:1187 +#: src/iac_code/commands/auth.py:1194 msgid "(not set)" msgstr "(未设置)" -#: src/iac_code/commands/auth.py:1204 +#: src/iac_code/commands/auth.py:1203 +msgid "AccessKey" +msgstr "AccessKey" + +#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +msgid "STS Token" +msgstr "STS 令牌" + +#: src/iac_code/commands/auth.py:1207 +msgid "RAM Role" +msgstr "RAM 角色" + +#: src/iac_code/commands/auth.py:1209 +msgid "OAuth Login (Browser)" +msgstr "OAuth 登录(浏览器)" + +#: src/iac_code/commands/auth.py:1215 +msgid "AccessKey ID" +msgstr "AccessKey ID" + +#: src/iac_code/commands/auth.py:1216 +msgid "AccessKey Secret" +msgstr "AccessKey 密钥" + +#: src/iac_code/commands/auth.py:1218 +msgid "RAM Role ARN" +msgstr "RAM 角色 ARN" + +#: src/iac_code/commands/auth.py:1219 +msgid "Session Name" +msgstr "会话名称" + +#: src/iac_code/commands/auth.py:1220 +msgid "OAuth Site Type" +msgstr "OAuth 站点类型" + +#: src/iac_code/commands/auth.py:1221 +msgid "OAuth Access Token" +msgstr "OAuth 访问令牌" + +#: src/iac_code/commands/auth.py:1222 +msgid "OAuth Refresh Token" +msgstr "OAuth 刷新令牌" + +#: src/iac_code/commands/auth.py:1223 +msgid "OAuth Access Token Expire" +msgstr "OAuth 访问令牌过期时间" + +#: src/iac_code/commands/auth.py:1224 +msgid "OAuth Refresh Token Expire" +msgstr "OAuth 刷新令牌过期时间" + +#: src/iac_code/commands/auth.py:1225 +msgid "STS Expiration" +msgstr "STS 过期时间" + +#: src/iac_code/commands/auth.py:1382 +msgid "China" +msgstr "中国" + +#: src/iac_code/commands/auth.py:1383 +msgid "International" +msgstr "国际" + +#: src/iac_code/commands/auth.py:1385 +msgid "Choose site type" +msgstr "选择站点类型" + +#: src/iac_code/commands/auth.py:1400 +#, python-brace-format +msgid "Alibaba Cloud OAuth login failed: {error}" +msgstr "阿里云 OAuth 登录失败:{error}" + +#: src/iac_code/commands/auth.py:1416 +msgid "Configured: Alibaba Cloud OAuth credentials saved" +msgstr "已配置:阿里云 OAuth 凭证已保存" + +#: src/iac_code/commands/auth.py:1428 msgid "Configure Alibaba Cloud credentials" msgstr "配置阿里云凭证" -#: src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1441 msgid "Reconfigure credential" msgstr "重新配置凭证" -#: src/iac_code/commands/auth.py:1230 +#: src/iac_code/commands/auth.py:1454 msgid "Select credential type" msgstr "选择凭证类型" -#: src/iac_code/commands/auth.py:1280 +#: src/iac_code/commands/auth.py:1510 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "已配置:阿里云凭证已保存到 ~/.iac-code" -#: src/iac_code/commands/auth.py:1287 +#: src/iac_code/commands/auth.py:1517 msgid "Configure Alibaba Cloud region" msgstr "配置阿里云地域" -#: src/iac_code/commands/auth.py:1313 +#: src/iac_code/commands/auth.py:1543 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "已配置:阿里云地域已保存到 ~/.iac-code" @@ -877,12 +898,8 @@ msgstr "没有活动的智能体循环。" #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "" -"Context compacted: {original} → {compacted} tokens ({percent_display} " -"reduction). Context usage: {usage_display}" -msgstr "" -"上下文已压缩:{original} → {compacted} tokens(减少 " -"{percent_display})。上下文用量:{usage_display}" +msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" +msgstr "上下文已压缩:{original} → {compacted} tokens(减少 {percent_display})。上下文用量:{usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -892,8 +909,7 @@ msgstr "调试命令需要上下文。" msgid "No active session." msgstr "没有活动的会话。" -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 -#: src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "没有已配置的提供商。请先运行 /auth。" @@ -987,9 +1003,7 @@ msgstr "记忆 '{name}' 已删除。" #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "" -"Model is managed by '{source}'. To change model, modify it in {source} or" -" switch provider via /auth." +msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." msgstr "模型由 '{source}' 管理。如需修改模型,请在 {source} 中调整,或通过 /auth 切换其他 Provider。" #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 @@ -1056,8 +1070,7 @@ msgstr "会话" msgid "Provider" msgstr "提供商" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 -#: src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 msgid "not configured" msgstr "未配置" @@ -1166,30 +1179,22 @@ msgstr "未知的 Provider 标识:'{key}'。请运行 /auth 进行配置。" #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "" -"No API key configured for provider '{provider}' (model: {model}). Run " -"/auth to configure." +msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." msgstr "提供商 '{provider}' 未配置 API 密钥(模型: {model})。请运行 /auth 进行配置。" #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct " -"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " -"suffix (e.g. {base_url}/v1)." -msgstr "" -"API 未返回数据。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 " -"后缀(如 {base_url}/v1)。" +"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " +"endpoints require a /v1 suffix (e.g. {base_url}/v1)." +msgstr "API 未返回数据。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 后缀(如 {base_url}/v1)。" #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is " -"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" -" /v1 suffix (e.g. {base_url}/v1)." -msgstr "" -"API 返回了无效响应。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 " -"后缀(如 {base_url}/v1)。" +"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" +"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." +msgstr "API 返回了无效响应。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 后缀(如 {base_url}/v1)。" #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1270,28 +1275,118 @@ msgstr "Anthropic 兼容" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " -"support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " -"mode (remove 'llm_source: qwenpaw' from settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " +"settings.yml)." msgstr "" "[QwenPaw 模式] 无法识别 provider '{provider_id}',iac-code 不支持该 provider。\n" "支持的 QwenPaw provider ID:{supported_ids}\n" -"解决方法:在 QwenPaw 中切换到已支持的 provider,或关闭 QwenPaw 模式(从 settings.yml 移除 " -"'llm_source: qwenpaw')。" +"解决方法:在 QwenPaw 中切换到已支持的 provider,或关闭 QwenPaw 模式(从 settings.yml 移除 'llm_source: qwenpaw')。" #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "无效的 --permission-mode {!r}。有效值:{}" -#: src/iac_code/services/permissions/pipeline.py:54 -#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "允许 {}?" +#: src/iac_code/services/providers/aliyun.py:144 +msgid "Alibaba Cloud OAuth site is missing." +msgstr "阿里云 OAuth 站点缺失。" + +#: src/iac_code/services/providers/aliyun.py:150 +msgid "Alibaba Cloud OAuth refresh token is missing." +msgstr "阿里云 OAuth 刷新令牌缺失。" + +#: src/iac_code/services/providers/aliyun.py:158 +msgid "Alibaba Cloud OAuth access token is missing." +msgstr "阿里云 OAuth 访问令牌缺失。" + +#: src/iac_code/services/providers/aliyun_oauth.py:83 +msgid "Run /auth and choose OAuth Login (Browser)." +msgstr "请运行 /auth 并选择 OAuth 登录(浏览器)。" + +#: src/iac_code/services/providers/aliyun_oauth.py:106 +#, python-brace-format +msgid "Unknown Aliyun OAuth site: {site_type}" +msgstr "未知的阿里云 OAuth 站点:{site_type}" + +#: src/iac_code/services/providers/aliyun_oauth.py:164 +msgid "Not found" +msgstr "未找到" + +#: src/iac_code/services/providers/aliyun_oauth.py:170 +msgid "invalid state" +msgstr "无效状态" + +#: src/iac_code/services/providers/aliyun_oauth.py:171 +msgid "Invalid state" +msgstr "无效状态" + +#: src/iac_code/services/providers/aliyun_oauth.py:176 +msgid "code not found" +msgstr "未找到授权码" + +#: src/iac_code/services/providers/aliyun_oauth.py:177 +msgid "Authorization code not found" +msgstr "未找到授权码" + +#: src/iac_code/services/providers/aliyun_oauth.py:181 +msgid "Authorization successful. You can close this window." +msgstr "授权成功。你可以关闭此窗口。" + +#: src/iac_code/services/providers/aliyun_oauth.py:212 +#, python-brace-format +msgid "No available callback port in range {start}-{end}" +msgstr "范围 {start}-{end} 内没有可用的回调端口" + +#: src/iac_code/services/providers/aliyun_oauth.py:227 +msgid "OAuth login cancelled." +msgstr "OAuth 登录已取消。" + +#: src/iac_code/services/providers/aliyun_oauth.py:278 +msgid "Open in your browser:" +msgstr "在浏览器中打开:" + +#: src/iac_code/services/providers/aliyun_oauth.py:299 +msgid "Waiting for browser authorization" +msgstr "等待浏览器授权" + +#: src/iac_code/services/providers/aliyun_oauth.py:300 +msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." +msgstr "1. 浏览器可能显示 official-cli,这是 Alibaba Cloud 官方 CLI OAuth 应用。" + +#: src/iac_code/services/providers/aliyun_oauth.py:302 +msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgstr "2. 如需分配应用,请分配给当前登录的 RAM 用户或 RAM 角色;不支持用户组。" + +#: src/iac_code/services/providers/aliyun_oauth.py:306 +msgid "" +"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " +"out of Alibaba Cloud and sign in again." +msgstr "3. 分配后关闭旧授权页,并再次运行 OAuth 登录(浏览器)。若仍失败,请退出 Alibaba Cloud 后重新登录。" + +#: src/iac_code/services/providers/aliyun_oauth.py:310 +msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgstr "4. STS 凭证会在可能时自动刷新;如果刷新失败,请重新运行 /auth。" + +#: src/iac_code/services/providers/aliyun_oauth.py:313 +msgid "Press Esc to cancel while waiting." +msgstr "按 Esc 可取消等待。" + +#: src/iac_code/services/providers/aliyun_oauth.py:321 +msgid "" +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" +" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " +"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +msgstr "" +"等待 OAuth 回调超时。如果 Alibaba Cloud 要求分配 official-cli 应用,请将它分配给当前实际登录的 RAM 用户或 RAM 角色。不支持用户组。然后关闭旧的授权页面,必要时退出 Alibaba " +"Cloud 并重新登录,再运行 /auth 重新选择 OAuth 登录(浏览器)。" + #: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." @@ -1312,9 +1407,7 @@ msgid "Skill disabled: {name}" msgstr "技能已禁用:{name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "" -"Review changed code for reuse, quality, and efficiency, then fix issues " -"found." +msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." msgstr "审查变更的代码的可复用性、质量和效率,然后修复发现的问题。" #: src/iac_code/tools/edit_file.py:116 @@ -1521,8 +1614,7 @@ msgstr "匹配到拒绝规则:{}" msgid "matched ask rule(s): {}" msgstr "匹配到询问规则:{}" -#: src/iac_code/tools/bash/permissions.py:154 -#: src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "匹配到允许规则:{}" @@ -1579,8 +1671,7 @@ msgstr "云API" msgid "Calling {action}..." msgstr "正在调用 {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 -#: src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "调用成功" @@ -1593,8 +1684,7 @@ msgstr "收到响应({count} 行)" msgid "CloudStack" msgstr "云资源栈" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 -#: src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "正在运行 {action}..." @@ -1739,11 +1829,11 @@ msgstr "导入完成" msgid "IMPORT_FAILED" msgstr "导入失败" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:171 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:172 msgid "Aliyun API" msgstr "阿里云 API" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:398 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:399 #, python-brace-format msgid "Call succeeded (RequestId: {request_id})" msgstr "调用成功(RequestId: {request_id})" @@ -1832,15 +1922,11 @@ msgstr "模板 JSON 语法错误(第 {line} 行,第 {col} 列):{msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "" -"Template {fmt} parse result is not an object (dict), please check the " -"template format" +msgid "Template {fmt} parse result is not an object (dict), please check the template format" msgstr "模板 {fmt} 解析结果不是对象(dict),请检查模板格式" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "" -"Template is missing ROSTemplateFormatVersion (ROS templates must include " -"this field, e.g. '2015-09-01')" +msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" msgstr "模板缺少 ROSTemplateFormatVersion 字段(ROS 模板必须包含此字段,如 '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 @@ -1854,9 +1940,7 @@ msgstr "Resources 必须是对象(dict),当前类型为 {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "" -"Resource '{name}' definition must be an object (dict), current type is " -"{type}" +msgid "Resource '{name}' definition must be an object (dict), current type is {type}" msgstr "资源 '{name}' 定义必须是对象(dict),当前类型为 {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 @@ -1870,9 +1954,7 @@ msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" msgstr "资源 '{name}' 的类型 '{wrong}' 不正确,应改为 '{correct}'" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "" -"Template structure validation found the following issues, please fix and " -"retry:" +msgid "Template structure validation found the following issues, please fix and retry:" msgstr "模板结构校验发现以下问题,请修复后重试:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 @@ -1909,14 +1991,12 @@ msgstr "调试模式" msgid "Log file" msgstr "日志文件" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 -#: src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "思考完成(耗时 {seconds:.1f}s)" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 -#: src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o 展开)" @@ -2117,9 +2197,7 @@ msgstr "(命令已复制到剪贴板)" #: src/iac_code/ui/repl.py:1451 #, python-brace-format -msgid "" -"Current model {model} does not support image input. Use /model to switch " -"to a vision-capable model." +msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." msgstr "当前模型 {model} 不支持图像输入。请使用 /model 切换到支持视觉的模型。" #: src/iac_code/ui/repl.py:1460 @@ -2128,9 +2206,7 @@ msgid "Image error: {err}" msgstr "图像错误:{err}" #: src/iac_code/ui/repl.py:1477 -msgid "" -"Failed to persist image to cache; it will only exist in memory for this " -"turn." +msgid "Failed to persist image to cache; it will only exist in memory for this turn." msgstr "无法将图像持久化到缓存;本轮对话期间它仅存在于内存中。" #: src/iac_code/ui/spinner.py:52 @@ -2321,9 +2397,7 @@ msgstr "{current} / {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "" -"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " -"cancel" +msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" msgstr "{count} 个技能 - 空格切换,Enter 保存,Tab 排序,Esc 取消" #: src/iac_code/ui/dialogs/skills_picker.py:171 @@ -2410,9 +2484,7 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code 在 Windows 上需要安装 Git for Windows。" #: src/iac_code/utils/platform.py:41 -msgid "" -"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " -"variable." +msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." msgstr "如果已安装但不在 PATH 中,请设置 IAC_CODE_GIT_BASH_PATH 环境变量。" #: src/iac_code/utils/platform.py:44 @@ -2424,9 +2496,7 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " 方式 1 - winget(需要能访问 github.com):" #: src/iac_code/utils/platform.py:52 -msgid "" -" Option 2 - if you cannot reach github.com, run this to install via " -"npmmirror:" +msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" msgstr " 方式 2 - 如果无法访问 github.com,运行以下命令通过 npmmirror 安装:" #~ msgid "toggle preview" @@ -2450,10 +2520,7 @@ msgstr " 方式 2 - 如果无法访问 github.com,运行以下命令通过 np #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter 不可用" -#~ msgid "" -#~ "LLM provider is locked by '{source}'." -#~ " To change, modify llm_source in " -#~ "settings.yml." +#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." #~ msgstr "LLM 提供商已被 '{source}' 锁定。如需修改,请调整 settings.yml 中的 llm_source 配置。" #~ msgid "Provider switched: {status}" @@ -2471,9 +2538,6 @@ msgstr " 方式 2 - 如果无法访问 github.com,运行以下命令通过 np #~ msgid "Cache create" #~ msgstr "缓存创建" -#~ msgid "" -#~ "{count} skills - Space to toggle, " -#~ "Enter to save, / to search, t " -#~ "to sort, Esc to cancel" +#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" #~ msgstr "{count} 个技能 - 空格切换,Enter 保存,/ 搜索,t 排序,Esc 取消" diff --git a/src/iac_code/services/providers/aliyun.py b/src/iac_code/services/providers/aliyun.py index d92ca20..f858e77 100644 --- a/src/iac_code/services/providers/aliyun.py +++ b/src/iac_code/services/providers/aliyun.py @@ -1,16 +1,18 @@ import json import os +import time from dataclasses import dataclass, field from pathlib import Path from typing import Any from iac_code.config import _load_yaml, _save_yaml, get_cloud_credentials_path +from iac_code.i18n import _ DEFAULT_REGION = "cn-hangzhou" DEFAULT_ALIYUN_CLI_CONFIG_PATH = os.path.expanduser("~/.aliyun/config.json") # Credential modes matching aliyun CLI -CREDENTIAL_MODES = ["AK", "StsToken", "RamRoleArn"] +CREDENTIAL_MODES = ["AK", "StsToken", "RamRoleArn", "OAuth"] # Fields definition for each credential mode # Each field: (name, label, sensitive) @@ -30,6 +32,17 @@ ("ram_role_arn", "RAM Role ARN", False), ("ram_session_name", "Session Name", False), ], + "OAuth": [ + ("oauth_site_type", "OAuth Site Type", False), + ("oauth_access_token", "OAuth Access Token", True), + ("oauth_refresh_token", "OAuth Refresh Token", True), + ("oauth_access_token_expire", "OAuth Access Token Expire", False), + ("oauth_refresh_token_expire", "OAuth Refresh Token Expire", False), + ("access_key_id", "AccessKey ID", True), + ("access_key_secret", "AccessKey Secret", True), + ("sts_token", "STS Token", True), + ("sts_expiration", "STS Expiration", False), + ], } # Display names for credential modes (English, translatable via i18n) @@ -37,6 +50,7 @@ "AK": "AccessKey", "StsToken": "STS Token", "RamRoleArn": "RAM Role", + "OAuth": "OAuth Login (Browser)", } @@ -47,8 +61,14 @@ class AliyunCredential: access_key_secret: str = "" region_id: str = field(default=DEFAULT_REGION) sts_token: str = "" + sts_expiration: int = 0 ram_role_arn: str = "" ram_session_name: str = "" + oauth_site_type: str = "" + oauth_access_token: str = "" + oauth_refresh_token: str = "" + oauth_access_token_expire: int = 0 + oauth_refresh_token_expire: int = 0 def mask_sensitive(value: str) -> str: @@ -95,6 +115,57 @@ def load(config_path: str | None = None) -> AliyunCredential | None: # Fall back to aliyun CLI config return AliyunCredentials._load_from_aliyun_cli(config_path) + @staticmethod + def refresh_oauth_if_needed( + credential: AliyunCredential, + *, + oauth_client: Any | None = None, + now: int | None = None, + ) -> AliyunCredential: + """Refresh OAuth-backed STS credentials before Alibaba Cloud API use.""" + from iac_code.services.providers.aliyun_oauth import ( + ACCESS_TOKEN_SKEW_SECONDS, + STS_SKEW_SECONDS, + AliyunOAuthClient, + AliyunOAuthReloginRequired, + get_oauth_site, + is_epoch_expired, + ) + + if credential.mode != "OAuth": + return credential + + current = int(time.time()) if now is None else now + has_sts = bool(credential.access_key_id and credential.access_key_secret and credential.sts_token) + if has_sts and not is_epoch_expired(credential.sts_expiration, current, STS_SKEW_SECONDS): + return credential + + if not credential.oauth_site_type: + raise AliyunOAuthReloginRequired(_("Alibaba Cloud OAuth site is missing.")) + + client = oauth_client or AliyunOAuthClient(get_oauth_site(credential.oauth_site_type)) + + if is_epoch_expired(credential.oauth_access_token_expire, current, ACCESS_TOKEN_SKEW_SECONDS): + if not credential.oauth_refresh_token: + raise AliyunOAuthReloginRequired(_("Alibaba Cloud OAuth refresh token is missing.")) + token = client.refresh_access_token(credential.oauth_refresh_token, now=current) + credential.oauth_access_token = token.access_token + credential.oauth_refresh_token = token.refresh_token + credential.oauth_access_token_expire = token.access_token_expire + credential.oauth_refresh_token_expire = token.refresh_token_expire + + if not credential.oauth_access_token: + raise AliyunOAuthReloginRequired(_("Alibaba Cloud OAuth access token is missing.")) + + sts = client.exchange_access_token_for_sts(credential.oauth_access_token) + credential.access_key_id = sts.access_key_id + credential.access_key_secret = sts.access_key_secret + credential.sts_token = sts.sts_token + credential.sts_expiration = sts.sts_expiration + + AliyunCredentials.save(credential) + return credential + @staticmethod def _load_from_iac_code_config() -> AliyunCredential | None: """Load credentials from ~/.iac-code/.cloud-credentials.yml.""" @@ -113,8 +184,14 @@ def _load_from_iac_code_config() -> AliyunCredential | None: access_key_secret=aliyun_data.get("access_key_secret", ""), region_id=aliyun_data.get("region_id", DEFAULT_REGION), sts_token=aliyun_data.get("sts_token", ""), + sts_expiration=int(aliyun_data.get("sts_expiration") or 0), ram_role_arn=aliyun_data.get("ram_role_arn", ""), ram_session_name=aliyun_data.get("ram_session_name", ""), + oauth_site_type=aliyun_data.get("oauth_site_type", ""), + oauth_access_token=aliyun_data.get("oauth_access_token", ""), + oauth_refresh_token=aliyun_data.get("oauth_refresh_token", ""), + oauth_access_token_expire=int(aliyun_data.get("oauth_access_token_expire") or 0), + oauth_refresh_token_expire=int(aliyun_data.get("oauth_refresh_token_expire") or 0), ) @staticmethod @@ -141,8 +218,14 @@ def _load_from_aliyun_cli(config_path: str | None = None) -> AliyunCredential | access_key_secret=profile.get("access_key_secret", ""), region_id=profile.get("region_id", DEFAULT_REGION), sts_token=profile.get("sts_token", ""), + sts_expiration=int(profile.get("sts_expiration") or 0), ram_role_arn=profile.get("ram_role_arn", ""), ram_session_name=profile.get("ram_session_name", ""), + oauth_site_type=profile.get("oauth_site_type", ""), + oauth_access_token=profile.get("oauth_access_token", ""), + oauth_refresh_token=profile.get("oauth_refresh_token", ""), + oauth_access_token_expire=int(profile.get("oauth_access_token_expire") or 0), + oauth_refresh_token_expire=int(profile.get("oauth_refresh_token_expire") or 0), ) @staticmethod @@ -175,7 +258,20 @@ def save( # Save fields relevant to the credential mode mode_fields = MODE_FIELDS.get(credential.mode, []) for field_name, _label, _sensitive in mode_fields: - aliyun_data[field_name] = getattr(credential, field_name, "") + value = getattr(credential, field_name, "") + if value in ("", None): + continue + if ( + field_name + in { + "sts_expiration", + "oauth_access_token_expire", + "oauth_refresh_token_expire", + } + and value == 0 + ): + continue + aliyun_data[field_name] = value cloud_creds["aliyun"] = aliyun_data _save_yaml(path, cloud_creds) @@ -197,20 +293,26 @@ def _save_to_aliyun_cli_format(credential: AliyunCredential, config_path: str) - except (json.JSONDecodeError, OSError): pass - updated_profile: dict[str, str] = { + updated_profile: dict[str, Any] = { "name": "default", "mode": credential.mode, "access_key_id": credential.access_key_id, "access_key_secret": credential.access_key_secret, "region_id": credential.region_id, "sts_token": credential.sts_token, + "sts_expiration": credential.sts_expiration, "ram_role_arn": credential.ram_role_arn, "ram_session_name": credential.ram_session_name, + "oauth_site_type": credential.oauth_site_type, + "oauth_access_token": credential.oauth_access_token, + "oauth_refresh_token": credential.oauth_refresh_token, + "oauth_access_token_expire": credential.oauth_access_token_expire, + "oauth_refresh_token_expire": credential.oauth_refresh_token_expire, } raw_profiles = data.get("profiles") - profiles: list[dict[str, str]] = ( - cast(list[dict[str, str]], raw_profiles) if isinstance(raw_profiles, list) else [] + profiles: list[dict[str, Any]] = ( + cast(list[dict[str, Any]], raw_profiles) if isinstance(raw_profiles, list) else [] ) for i, profile in enumerate(profiles): diff --git a/src/iac_code/services/providers/aliyun_oauth.py b/src/iac_code/services/providers/aliyun_oauth.py new file mode 100644 index 0000000..7d3220b --- /dev/null +++ b/src/iac_code/services/providers/aliyun_oauth.py @@ -0,0 +1,583 @@ +import base64 +import hashlib +import queue +import secrets +import threading +import time +import webbrowser +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timezone +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from typing import Any +from urllib.parse import parse_qs, urlencode, urlparse + +import httpx + +from iac_code.i18n import _ + +CALLBACK_HOST = "127.0.0.1" +CALLBACK_PATH = "/cli/callback" +CALLBACK_PORTS = tuple(range(12345, 12350)) +DEFAULT_CALLBACK_TIMEOUT_SECONDS = 300 +ACCESS_TOKEN_SKEW_SECONDS = 60 +STS_SKEW_SECONDS = 120 +PERMANENT_OAUTH_ERROR_CODES = {"invalid_grant", "invalid_client", "unauthorized_client", "invalid_token"} + + +@dataclass(frozen=True) +class AliyunOAuthSite: + site_type: str + display_name: str + client_id: str + signin_base_url: str + oauth_base_url: str + + +OAUTH_SITES: dict[str, AliyunOAuthSite] = { + "CN": AliyunOAuthSite( + site_type="CN", + display_name="China", + client_id="4038181954557748008", + signin_base_url="https://signin.aliyun.com", + oauth_base_url="https://oauth.aliyun.com", + ), + "INTL": AliyunOAuthSite( + site_type="INTL", + display_name="International", + client_id="4103531455503354461", + signin_base_url="https://signin.alibabacloud.com", + oauth_base_url="https://oauth.alibabacloud.com", + ), +} + + +@dataclass(frozen=True) +class OAuthToken: + access_token: str + refresh_token: str + access_token_expire: int + refresh_token_expire: int = 0 + + +@dataclass(frozen=True) +class OAuthStsCredentials: + access_key_id: str + access_key_secret: str + sts_token: str + sts_expiration: int + + +class AliyunOAuthError(RuntimeError): + def __init__(self, message: str, *, error_code: str | None = None, status_code: int | None = None) -> None: + super().__init__(message) + self.error_code = error_code + self.status_code = status_code + + +class AliyunOAuthCancelledError(AliyunOAuthError): + pass + + +def oauth_relogin_hint() -> str: + return _("Run /auth and choose OAuth Login (Browser).") + + +class AliyunOAuthReloginRequired(AliyunOAuthError): # noqa: N818 + def __init__(self, message: str, *, error_code: str | None = None, status_code: int | None = None) -> None: + hint = oauth_relogin_hint() + if hint not in message: + message = "{} {}".format(message, hint) + super().__init__(message, error_code=error_code, status_code=status_code) + + +def get_oauth_site(site_type: str) -> AliyunOAuthSite: + normalized = site_type.strip().lower() + aliases = { + "cn": "CN", + "china": "CN", + "aliyun": "CN", + "intl": "INTL", + "international": "INTL", + "alibabacloud": "INTL", + } + site_key = aliases.get(normalized) + if not site_key: + raise AliyunOAuthError(_("Unknown Aliyun OAuth site: {site_type}").format(site_type=site_type)) + return OAUTH_SITES[site_key] + + +def oauth_site_options() -> list[tuple[str, str]]: + return [("CN", "China"), ("INTL", "International")] + + +def generate_state() -> str: + return secrets.token_urlsafe(16) + + +def generate_code_verifier() -> str: + return secrets.token_urlsafe(96)[:128] + + +def generate_code_challenge(code_verifier: str) -> str: + digest = hashlib.sha256(code_verifier.encode("ascii")).digest() + return base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=") + + +def build_authorization_url(site: AliyunOAuthSite, redirect_uri: str, state: str, code_challenge: str) -> str: + query = urlencode( + { + "response_type": "code", + "client_id": site.client_id, + "redirect_uri": redirect_uri, + "state": state, + "code_challenge": code_challenge, + "code_challenge_method": "S256", + } + ) + return "{}/oauth2/v1/auth?{}".format(site.signin_base_url.rstrip("/"), query) + + +class OAuthCallbackServer: + def __init__( + self, + ports: tuple[int, ...] = CALLBACK_PORTS, + timeout_seconds: int = DEFAULT_CALLBACK_TIMEOUT_SECONDS, + ) -> None: + self.ports = ports + self.timeout_seconds = timeout_seconds + self.redirect_uri = "" + self._results: queue.Queue[tuple[str, str]] = queue.Queue(maxsize=1) + self._server: ThreadingHTTPServer | None = None + self._thread: threading.Thread | None = None + + def start(self, expected_state: str) -> None: + if self._server is not None: + return + + result_queue = self._results + + class CallbackHandler(BaseHTTPRequestHandler): + def do_GET(self) -> None: # noqa: N802 + parsed_url = urlparse(self.path) + if parsed_url.path != CALLBACK_PATH: + self._send_plain_response(404, _("Not found")) + return + + query = parse_qs(parsed_url.query) + state = query.get("state", [""])[0] + if state != expected_state: + self._put_result(("error", _("invalid state"))) + self._send_plain_response(400, _("Invalid state")) + return + + code = query.get("code", [""])[0] + if not code: + self._put_result(("error", _("code not found"))) + self._send_plain_response(400, _("Authorization code not found")) + return + + self._put_result(("code", code)) + self._send_plain_response(200, _("Authorization successful. You can close this window.")) + + def log_message(self, format: str, *args: Any) -> None: + return + + def _put_result(self, result: tuple[str, str]) -> None: + try: + result_queue.put_nowait(result) + except queue.Full: + return + + def _send_plain_response(self, status_code: int, body: str) -> None: + body_bytes = body.encode("utf-8") + self.send_response(status_code) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body_bytes))) + self.end_headers() + self.wfile.write(body_bytes) + + last_error: OSError | None = None + for port in self.ports: + try: + self._server = ThreadingHTTPServer((CALLBACK_HOST, port), CallbackHandler) + except OSError as exc: + last_error = exc + continue + self.redirect_uri = "http://{}:{}{}".format(CALLBACK_HOST, port, CALLBACK_PATH) + self._thread = threading.Thread(target=self._server.serve_forever, daemon=True) + self._thread.start() + return + + message = _("No available callback port in range {start}-{end}").format( + start=self.ports[0], + end=self.ports[-1], + ) + raise AliyunOAuthError(message) from last_error + + def wait_for_code( + self, + *, + cancel_event: threading.Event | None = None, + poll_interval_seconds: float = 0.1, + ) -> str: + deadline = time.monotonic() + self.timeout_seconds + while True: + if cancel_event is not None and cancel_event.is_set(): + raise AliyunOAuthCancelledError(_("OAuth login cancelled.")) + + remaining = deadline - time.monotonic() + if remaining <= 0: + raise AliyunOAuthError(oauth_callback_timeout_message()) + + try: + result_type, value = self._results.get(timeout=min(poll_interval_seconds, remaining)) + break + except queue.Empty: + continue + + if result_type == "error": + raise AliyunOAuthError(value) + return value + + def close(self) -> None: + server = self._server + thread = self._thread + self._server = None + self._thread = None + if server is None: + return + server.shutdown() + server.server_close() + if thread is not None: + thread.join(timeout=1) + + +def run_browser_oauth_flow( + site_type: str, + *, + oauth_client: Any | None = None, + browser_opener: Callable[[str], bool] = webbrowser.open, + callback_server_factory: Callable[[], Any] | None = None, + writer: Callable[[str], None] = print, + cancel_event: threading.Event | None = None, + now: int | None = None, +) -> OAuthToken: + site = get_oauth_site(site_type) + client = oauth_client or AliyunOAuthClient(site) + server = callback_server_factory() if callback_server_factory is not None else OAuthCallbackServer() + state = generate_state() + code_verifier = generate_code_verifier() + code_challenge = generate_code_challenge(code_verifier) + + try: + server.start(state) + url = build_authorization_url(site, server.redirect_uri, state, code_challenge) + for line in oauth_browser_login_guidance(): + writer(line) + writer(" {}".format(_("Open in your browser:"))) + writer(" {}".format(url)) + try: + browser_opener(url) + except Exception: + pass + + code = server.wait_for_code(cancel_event=cancel_event) if cancel_event is not None else server.wait_for_code() + return client.exchange_code_for_token( + code=code, + redirect_uri=server.redirect_uri, + code_verifier=code_verifier, + now=now, + ) + finally: + server.close() + + +def oauth_browser_login_guidance() -> list[str]: + messages = [ + "", + _("Waiting for browser authorization"), + _("1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application."), + _( + "2. If assignment is required, assign the RAM user or RAM role that is signed in. " + "User groups are not supported." + ), + _( + "3. After assignment, close the old authorization page and run OAuth Login (Browser) again. " + "If it still fails, sign out of Alibaba Cloud and sign in again." + ), + _( + "4. STS credentials refresh when possible until Alibaba Cloud expires them. " + "If refresh fails, run /auth again." + ), + _("Press Esc to cancel while waiting."), + "", + ] + return ["" if not message else " {}".format(message) for message in messages] + + +def oauth_callback_timeout_message() -> str: + return _( + "Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, " + "assign it to the exact RAM user or RAM role currently signed in. User groups are not supported. " + "Then close the old authorization page, sign out of Alibaba Cloud and sign in again if needed, " + "and run /auth to choose OAuth Login (Browser) again." + ) + + +def is_epoch_expired(expiration: int, now: int | None = None, skew_seconds: int = 0) -> bool: + if expiration <= 0: + return True + current_time = int(time.time()) if now is None else now + return expiration <= current_time + skew_seconds + + +def parse_sts_exchange_response(data: dict[str, Any]) -> OAuthStsCredentials: + access_key_id = _first_present(data, "accessKeyId", "AccessKeyId") + access_key_secret = _first_present(data, "accessKeySecret", "AccessKeySecret") + sts_token = _first_present(data, "securityToken", "SecurityToken") + expiration = _first_present(data, "expiration", "Expiration") + + missing = [ + name + for name, value in ( + ("accessKeyId", access_key_id), + ("accessKeySecret", access_key_secret), + ("securityToken", sts_token), + ("expiration", expiration), + ) + if value in (None, "") + ] + if missing: + raise AliyunOAuthError("STS exchange response missing required field(s): {}".format(", ".join(missing))) + + return OAuthStsCredentials( + access_key_id=str(access_key_id), + access_key_secret=str(access_key_secret), + sts_token=str(sts_token), + sts_expiration=_parse_expiration(expiration), + ) + + +class AliyunOAuthClient: + def __init__(self, site: AliyunOAuthSite, http_client: httpx.Client | None = None) -> None: + self.site = site + self.http_client = http_client or httpx.Client(timeout=30.0) + + def exchange_code_for_token( + self, + code: str, + redirect_uri: str, + code_verifier: str, + now: int | None = None, + ) -> OAuthToken: + response = self._post( + "{}/v1/token".format(self.site.oauth_base_url.rstrip("/")), + "exchange authorization code for token", + sensitive_values=(code, code_verifier), + data={ + "grant_type": "authorization_code", + "code": code, + "client_id": self.site.client_id, + "redirect_uri": redirect_uri, + "code_verifier": code_verifier, + }, + ) + self._raise_for_oauth_error( + response, + "exchange authorization code for token", + sensitive_values=(code, code_verifier), + ) + data = self._json_response(response, "exchange authorization code for token") + return self._parse_token_response( + data, + operation="exchange authorization code for token", + fallback_refresh_token=None, + now=now, + ) + + def refresh_access_token(self, refresh_token: str, now: int | None = None) -> OAuthToken: + response = self._post( + "{}/v1/token".format(self.site.oauth_base_url.rstrip("/")), + "refresh access token", + sensitive_values=(refresh_token,), + data={ + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "client_id": self.site.client_id, + }, + ) + self._raise_for_oauth_error(response, "refresh access token", sensitive_values=(refresh_token,)) + data = self._json_response(response, "refresh access token") + return self._parse_token_response( + data, + operation="refresh access token", + fallback_refresh_token=refresh_token, + now=now, + ) + + def exchange_access_token_for_sts(self, access_token: str) -> OAuthStsCredentials: + response = self._post( + "{}/v1/exchange".format(self.site.oauth_base_url.rstrip("/")), + "exchange access token for STS", + sensitive_values=(access_token,), + headers={"Authorization": "Bearer {}".format(access_token), "Content-Type": "application/json"}, + json={}, + ) + self._raise_for_oauth_error( + response, + "exchange access token for STS", + sensitive_values=(access_token,), + ) + data = self._json_response(response, "exchange access token for STS") + return parse_sts_exchange_response(data) + + def _parse_token_response( + self, + data: dict[str, Any], + *, + operation: str, + fallback_refresh_token: str | None, + now: int | None, + ) -> OAuthToken: + access_token = data.get("access_token") + refresh_token = data.get("refresh_token") or fallback_refresh_token + expires_in = data.get("expires_in") + missing = [ + name + for name, value in ( + ("access_token", access_token), + ("refresh_token", refresh_token), + ("expires_in", expires_in), + ) + if value in (None, "") + ] + if missing: + raise AliyunOAuthError("{} response missing required field(s): {}".format(operation, ", ".join(missing))) + + current_time = int(time.time()) if now is None else now + access_token_expire = current_time + _parse_int(expires_in, "expires_in") + refresh_expires_in = data.get("refresh_expires_in") + refresh_token_expire = 0 + if refresh_expires_in not in (None, ""): + refresh_token_expire = current_time + _parse_int(refresh_expires_in, "refresh_expires_in") + + return OAuthToken( + access_token=str(access_token), + refresh_token=str(refresh_token), + access_token_expire=access_token_expire, + refresh_token_expire=refresh_token_expire, + ) + + def _raise_for_oauth_error( + self, + response: httpx.Response, + operation: str, + sensitive_values: tuple[str, ...] = (), + ) -> None: + if response.status_code == 200: + return + + body = _response_json_or_empty(response) + error_code = _string_or_none(body.get("error")) + error_description = _redact_sensitive_values(_string_or_none(body.get("error_description")), sensitive_values) + message_parts = ["{} failed with status {}".format(operation, response.status_code)] + if error_code: + message_parts.append("error={}".format(error_code)) + if error_description: + message_parts.append("error_description={}".format(error_description)) + if len(message_parts) > 1: + message = ": ".join([message_parts[0], ", ".join(message_parts[1:])]) + else: + message = message_parts[0] + + error_cls = AliyunOAuthReloginRequired if error_code in PERMANENT_OAUTH_ERROR_CODES else AliyunOAuthError + raise error_cls(message, error_code=error_code, status_code=response.status_code) + + def _post( + self, + url: str, + operation: str, + *, + sensitive_values: tuple[str, ...], + **kwargs: Any, + ) -> httpx.Response: + try: + return self.http_client.post(url, **kwargs) + except httpx.HTTPError as exc: + detail = _redact_sensitive_values(str(exc), sensitive_values) or exc.__class__.__name__ + raise AliyunOAuthError("{} request failed: {}".format(operation, detail)) from exc + + def _json_response(self, response: httpx.Response, operation: str) -> dict[str, Any]: + try: + data = response.json() + except ValueError as exc: + raise AliyunOAuthError( + "{} response was not valid JSON".format(operation), + status_code=response.status_code, + ) from exc + if not isinstance(data, dict): + raise AliyunOAuthError( + "{} response JSON was not an object".format(operation), + status_code=response.status_code, + ) + return data + + +def _first_present(data: dict[str, Any], *keys: str) -> Any: + for key in keys: + if key in data: + return data[key] + return None + + +def _parse_expiration(value: Any) -> int: + if isinstance(value, int): + return value + if isinstance(value, float): + return int(value) + if isinstance(value, str): + stripped = value.strip() + if stripped.isdigit(): + return int(stripped) + if stripped.endswith("Z"): + stripped = "{}+00:00".format(stripped[:-1]) + try: + parsed = datetime.fromisoformat(stripped) + except ValueError as exc: + raise AliyunOAuthError("STS exchange response has invalid expiration") from exc + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=timezone.utc) + return int(parsed.timestamp()) + raise AliyunOAuthError("STS exchange response has invalid expiration") + + +def _parse_int(value: Any, field_name: str) -> int: + try: + return int(value) + except (TypeError, ValueError) as exc: + raise AliyunOAuthError("OAuth token response has invalid {}".format(field_name)) from exc + + +def _response_json_or_empty(response: httpx.Response) -> dict[str, Any]: + try: + data = response.json() + except ValueError: + return {} + return data if isinstance(data, dict) else {} + + +def _string_or_none(value: Any) -> str | None: + if value in (None, ""): + return None + return str(value) + + +def _redact_sensitive_values(value: str | None, sensitive_values: tuple[str, ...]) -> str | None: + if value is None: + return None + redacted = value + for sensitive_value in sensitive_values: + if sensitive_value: + redacted = redacted.replace(sensitive_value, "[REDACTED]") + return redacted diff --git a/src/iac_code/tools/cloud/aliyun/aliyun_api.py b/src/iac_code/tools/cloud/aliyun/aliyun_api.py index 2646661..1ae7b0d 100644 --- a/src/iac_code/tools/cloud/aliyun/aliyun_api.py +++ b/src/iac_code/tools/cloud/aliyun/aliyun_api.py @@ -15,7 +15,8 @@ from iac_code.i18n import _ from iac_code.services.cloud_credentials import CloudCredentials -from iac_code.services.providers.aliyun import AliyunCredential +from iac_code.services.providers.aliyun import AliyunCredential, AliyunCredentials +from iac_code.services.providers.aliyun_oauth import AliyunOAuthError from iac_code.services.telemetry import add_metric, log_event from iac_code.services.telemetry.names import Events, Metrics from iac_code.services.telemetry.sanitize import sanitize_error_message @@ -334,7 +335,7 @@ def _build_config(credential: AliyunCredential, endpoint: str, region_id: str) - mode = credential.mode user_agent = build_user_agent() - if mode == "StsToken": + if mode in {"StsToken", "OAuth"}: return open_api_models.Config( access_key_id=credential.access_key_id, access_key_secret=credential.access_key_secret, @@ -431,6 +432,12 @@ async def execute(self, *, tool_input: dict[str, Any], context: ToolContext) -> "Run 'iac-code auth' and select 'Cloud Provider' to configure." ) + if credential.mode == "OAuth": + try: + credential = AliyunCredentials.refresh_oauth_if_needed(credential) + except AliyunOAuthError as exc: + return ToolResult.error(str(exc)) + endpoint = ( self._get_endpoint(product, region) or self._discover_endpoint(product, region, credential) diff --git a/src/iac_code/tools/cloud/aliyun/ros_client.py b/src/iac_code/tools/cloud/aliyun/ros_client.py index 787a38d..3a7c389 100644 --- a/src/iac_code/tools/cloud/aliyun/ros_client.py +++ b/src/iac_code/tools/cloud/aliyun/ros_client.py @@ -1,7 +1,8 @@ from alibabacloud_ros20190910.client import Client as RosClient from alibabacloud_tea_openapi import models as open_api_models -from iac_code.services.providers.aliyun import AliyunCredential +from iac_code.services.providers.aliyun import AliyunCredential, AliyunCredentials +from iac_code.services.providers.aliyun_oauth import AliyunOAuthError from iac_code.tools.cloud.aliyun.user_agent import build_user_agent @@ -14,6 +15,12 @@ def create(credential: AliyunCredential | None, region_id: str = "") -> RosClien "Run 'iac-code auth' and select 'Cloud Provider' to configure." ) + if credential.mode == "OAuth": + try: + credential = AliyunCredentials.refresh_oauth_if_needed(credential) + except AliyunOAuthError as exc: + raise ValueError(str(exc)) from exc + effective_region = region_id or credential.region_id if not effective_region: raise ValueError("Region not configured. Run 'iac-code auth' and configure the region for Alibaba Cloud.") @@ -25,7 +32,7 @@ def _build_config(credential: AliyunCredential, region_id: str) -> open_api_mode mode = credential.mode user_agent = build_user_agent() - if mode == "StsToken": + if mode in {"StsToken", "OAuth"}: return open_api_models.Config( access_key_id=credential.access_key_id, access_key_secret=credential.access_key_secret, diff --git a/tests/commands/test_auth_flows.py b/tests/commands/test_auth_flows.py index b4e5f9f..05e99cb 100644 --- a/tests/commands/test_auth_flows.py +++ b/tests/commands/test_auth_flows.py @@ -1,5 +1,6 @@ """Tests for auth_command and _auth_flow orchestration.""" +from datetime import datetime from unittest.mock import MagicMock import pytest @@ -7,11 +8,14 @@ from iac_code.commands.auth import ( _BACK, _aliyun_auth_flow, + _aliyun_credential_flow, _aliyun_region_flow, _auth_flow, _cloud_auth_flow, _cloud_provider_display, _llm_auth_flow, + _oauth_escape_cancel_event, + _render_credential_info, auth_command, ) @@ -341,6 +345,31 @@ def select_side_effect(title, options, default_index=0): assert _aliyun_auth_flow() is _BACK assert calls["select"] == 2 + def test_render_credential_info_formats_oauth_expiration_as_local_datetime(self, monkeypatch): + from iac_code.services.providers.aliyun import AliyunCredential + + writes: list[str] = [] + monkeypatch.setattr("iac_code.commands.auth._write", writes.append) + credential = AliyunCredential( + mode="OAuth", + region_id="cn-hangzhou", + oauth_site_type="CN", + oauth_access_token="access-token", + oauth_refresh_token="refresh-token", + oauth_access_token_expire=1780397040, + sts_expiration=1780397041, + ) + + _render_credential_info(credential, "iac-code") + + output = "".join(writes) + expected_access_time = datetime.fromtimestamp(1780397040).astimezone().strftime("%Y-%m-%d %H:%M:%S") + expected_sts_time = datetime.fromtimestamp(1780397041).astimezone().strftime("%Y-%m-%d %H:%M:%S") + assert expected_access_time in output + assert expected_sts_time in output + assert "1780397040" not in output + assert "1780397041" not in output + def test_region_flow_updates_existing_credential(self, monkeypatch): from iac_code.services.providers.aliyun import AliyunCredential @@ -389,6 +418,256 @@ def test_region_flow_creates_new_credential_when_missing(self, monkeypatch): assert "Configured" in result assert saved["credential"].region_id == "cn-hangzhou" + def test_aliyun_credential_flow_shows_oauth_mode(self, monkeypatch): + options_seen = [] + + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials._load_from_iac_code_config", + lambda: None, + ) + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials.load_from_aliyun_cli", + lambda config_path=None: None, + ) + + def fake_select(title, options, default_index=0): + if "credential type" in title.lower(): + options_seen.extend(options) + return None + + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + + assert _aliyun_credential_flow() is _BACK + assert "OAuth Login (Browser)" in options_seen + + def test_aliyun_credential_flow_oauth_login_saves_credentials(self, monkeypatch): + from iac_code.services.providers.aliyun_oauth import OAuthStsCredentials, OAuthToken + + saved = {} + + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials._load_from_iac_code_config", + lambda: None, + ) + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials.load_from_aliyun_cli", + lambda config_path=None: None, + ) + + def fake_select(title, options, default_index=0): + if "credential type" in title.lower(): + return options.index("OAuth Login (Browser)") + if "site type" in title.lower(): + return options.index("China") + return None + + class FakeOAuthClient: + def __init__(self, site): + self.site = site + + def exchange_access_token_for_sts(self, access_token): + assert access_token == "access-token" + return OAuthStsCredentials("tmp-ak", "tmp-sk", "tmp-sts", 1798794000) + + def fake_browser_oauth_flow(site_type, oauth_client=None, cancel_event=None): + assert site_type == "CN" + assert isinstance(oauth_client, FakeOAuthClient) + assert cancel_event is not None + return OAuthToken("access-token", "refresh-token", 1798790400, 1822320000) + + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + monkeypatch.setattr( + "iac_code.services.providers.aliyun_oauth.run_browser_oauth_flow", + fake_browser_oauth_flow, + ) + monkeypatch.setattr("iac_code.services.providers.aliyun_oauth.AliyunOAuthClient", FakeOAuthClient) + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials.save", + lambda credential: saved.setdefault("credential", credential), + ) + + result = _aliyun_credential_flow() + + assert result == "Configured: Alibaba Cloud OAuth credentials saved" + credential = saved["credential"] + assert credential.mode == "OAuth" + assert credential.region_id == "cn-hangzhou" + assert credential.oauth_site_type == "CN" + assert credential.oauth_access_token == "access-token" + assert credential.oauth_refresh_token == "refresh-token" + assert credential.oauth_access_token_expire == 1798790400 + assert credential.oauth_refresh_token_expire == 1822320000 + assert credential.access_key_id == "tmp-ak" + assert credential.access_key_secret == "tmp-sk" + assert credential.sts_token == "tmp-sts" + assert credential.sts_expiration == 1798794000 + + def test_aliyun_credential_flow_oauth_preserves_existing_region(self, monkeypatch): + from iac_code.services.providers.aliyun import AliyunCredential + from iac_code.services.providers.aliyun_oauth import OAuthStsCredentials, OAuthToken + + existing = AliyunCredential(region_id="cn-shanghai") + saved = {} + + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials._load_from_iac_code_config", + lambda: existing, + ) + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials.load_from_aliyun_cli", + lambda config_path=None: None, + ) + monkeypatch.setattr( + "iac_code.commands.auth._select_with_info", + lambda title, options, info_renderer=None, default_index=0: 0, + ) + + def fake_select(title, options, default_index=0): + if "credential type" in title.lower(): + return options.index("OAuth Login (Browser)") + if "site type" in title.lower(): + return options.index("International") + return None + + class FakeOAuthClient: + def __init__(self, site): + self.site = site + + def exchange_access_token_for_sts(self, access_token): + assert access_token == "access-token" + return OAuthStsCredentials("tmp-ak", "tmp-sk", "tmp-sts", 1798794000) + + def fake_browser_oauth_flow(site_type, oauth_client=None, cancel_event=None): + assert site_type == "INTL" + assert isinstance(oauth_client, FakeOAuthClient) + assert cancel_event is not None + return OAuthToken("access-token", "refresh-token", 1798790400, 1822320000) + + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + monkeypatch.setattr( + "iac_code.services.providers.aliyun_oauth.run_browser_oauth_flow", + fake_browser_oauth_flow, + ) + monkeypatch.setattr("iac_code.services.providers.aliyun_oauth.AliyunOAuthClient", FakeOAuthClient) + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials.save", + lambda credential: saved.setdefault("credential", credential), + ) + + _aliyun_credential_flow() + + assert saved["credential"].region_id == "cn-shanghai" + assert saved["credential"].oauth_site_type == "INTL" + + def test_aliyun_credential_flow_oauth_error_returns_message_without_saving(self, monkeypatch): + from iac_code.services.providers.aliyun_oauth import AliyunOAuthError + + saved = {} + + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials._load_from_iac_code_config", + lambda: None, + ) + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials.load_from_aliyun_cli", + lambda config_path=None: None, + ) + + def fake_select(title, options, default_index=0): + if "credential type" in title.lower(): + return options.index("OAuth Login (Browser)") + if "site type" in title.lower(): + return options.index("China") + return None + + def fail_oauth(site_type, oauth_client=None, cancel_event=None): + assert site_type == "CN" + assert oauth_client is not None + assert cancel_event is not None + raise AliyunOAuthError("No available callback port") + + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + monkeypatch.setattr("iac_code.services.providers.aliyun_oauth.run_browser_oauth_flow", fail_oauth) + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials.save", + lambda credential: saved.setdefault("credential", credential), + ) + + result = _aliyun_credential_flow() + + assert result == "Alibaba Cloud OAuth login failed: No available callback port" + assert saved == {} + + def test_aliyun_credential_flow_oauth_cancel_returns_to_mode_selection(self, monkeypatch): + from iac_code.services.providers.aliyun_oauth import AliyunOAuthCancelledError + + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials._load_from_iac_code_config", + lambda: None, + ) + monkeypatch.setattr( + "iac_code.services.providers.aliyun.AliyunCredentials.load_from_aliyun_cli", + lambda config_path=None: None, + ) + + selections = iter(["credential", "site", "cancel"]) + + def fake_select(title, options, default_index=0): + step = next(selections) + if step == "credential": + return options.index("OAuth Login (Browser)") + if step == "site": + return options.index("China") + return None + + def cancel_oauth(site_type, oauth_client=None, cancel_event=None): + assert site_type == "CN" + assert cancel_event is not None + raise AliyunOAuthCancelledError("OAuth login cancelled.") + + monkeypatch.setattr("iac_code.commands.auth._select", fake_select) + monkeypatch.setattr("iac_code.services.providers.aliyun_oauth.run_browser_oauth_flow", cancel_oauth) + + assert _aliyun_credential_flow() is _BACK + + def test_oauth_escape_cancel_event_uses_cbreak_mode_to_preserve_output_newlines(self, monkeypatch): + calls: list[tuple] = [] + + class FakeStdin: + def isatty(self): + return True + + def fileno(self): + return 42 + + class FakeThread: + def __init__(self, target, args, daemon=False): + calls.append(("thread", target.__name__, daemon)) + + def start(self): + calls.append(("start",)) + + def join(self, timeout=None): + calls.append(("join", timeout)) + + def fail_setraw(fd): + raise AssertionError("OAuth Esc listener should not use raw mode because it breaks terminal newlines") + + monkeypatch.setattr("iac_code.commands.auth._IS_WIN32", False) + monkeypatch.setattr("iac_code.commands.auth.sys.stdin", FakeStdin()) + monkeypatch.setattr("iac_code.commands.auth.threading.Thread", FakeThread) + monkeypatch.setattr("termios.tcgetattr", lambda fd: calls.append(("tcgetattr", fd)) or "old-settings") + monkeypatch.setattr("termios.tcsetattr", lambda fd, when, settings: calls.append(("tcsetattr", fd, settings))) + monkeypatch.setattr("tty.setraw", fail_setraw) + monkeypatch.setattr("tty.setcbreak", lambda fd: calls.append(("setcbreak", fd))) + + with _oauth_escape_cancel_event(): + calls.append(("body",)) + + assert ("setcbreak", 42) in calls + assert ("body",) in calls + assert ("tcsetattr", 42, "old-settings") in calls + class TestAuthLlmSourceLock: def test_auth_flow_always_shows_category_selection(self, monkeypatch): diff --git a/tests/services/providers/test_aliyun.py b/tests/services/providers/test_aliyun.py index 5dcff4f..f1a05f4 100644 --- a/tests/services/providers/test_aliyun.py +++ b/tests/services/providers/test_aliyun.py @@ -2,6 +2,7 @@ import os from unittest.mock import patch +import pytest import yaml from iac_code.services.providers.aliyun import ( @@ -9,6 +10,7 @@ AliyunCredentials, mask_sensitive, ) +from iac_code.services.providers.aliyun_oauth import AliyunOAuthReloginRequired, OAuthStsCredentials, OAuthToken class TestAliyunCredential: @@ -58,6 +60,33 @@ def test_ram_role_arn_mode(self): assert cred.ram_role_arn == "acs:ram::123:role/test" assert cred.ram_session_name == "session1" + def test_oauth_mode_fields(self): + cred = AliyunCredential( + mode="OAuth", + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + sts_expiration=1798794000, + oauth_site_type="CN", + oauth_access_token="oauth-access", + oauth_refresh_token="oauth-refresh", + oauth_access_token_expire=1798790400, + oauth_refresh_token_expire=1801382400, + region_id="cn-hangzhou", + ) + + assert cred.mode == "OAuth" + assert cred.access_key_id == "tmp-ak" + assert cred.access_key_secret == "tmp-sk" + assert cred.sts_token == "tmp-sts" + assert cred.sts_expiration == 1798794000 + assert cred.oauth_site_type == "CN" + assert cred.oauth_access_token == "oauth-access" + assert cred.oauth_refresh_token == "oauth-refresh" + assert cred.oauth_access_token_expire == 1798790400 + assert cred.oauth_refresh_token_expire == 1801382400 + assert cred.region_id == "cn-hangzhou" + class TestMaskSensitive: def test_mask_normal_string(self): @@ -286,6 +315,48 @@ def test_load_from_config_file(self, tmp_path): assert cred.region_id == "cn-shenzhen" assert cred.mode == "AK" + def test_load_oauth_from_aliyun_cli_default_profile(self, tmp_path): + config_file = tmp_path / "config.json" + config = { + "current": "default", + "profiles": [ + { + "name": "default", + "mode": "OAuth", + "access_key_id": "tmp-ak", + "access_key_secret": "tmp-sk", + "sts_token": "tmp-sts", + "sts_expiration": 1798794000, + "oauth_site_type": "CN", + "oauth_access_token": "oauth-access", + "oauth_refresh_token": "oauth-refresh", + "oauth_access_token_expire": 1798790400, + "oauth_refresh_token_expire": 1801382400, + "region_id": "cn-hangzhou", + } + ], + } + config_file.write_text(json.dumps(config)) + + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("ALIBABA_CLOUD_ACCESS_KEY_ID", None) + os.environ.pop("ALIBABA_CLOUD_ACCESS_KEY_SECRET", None) + os.environ.pop("ALIBABA_CLOUD_REGION_ID", None) + cred = AliyunCredentials.load(config_path=str(config_file)) + + assert cred is not None + assert cred.mode == "OAuth" + assert cred.access_key_id == "tmp-ak" + assert cred.access_key_secret == "tmp-sk" + assert cred.sts_token == "tmp-sts" + assert cred.sts_expiration == 1798794000 + assert cred.oauth_site_type == "CN" + assert cred.oauth_access_token == "oauth-access" + assert cred.oauth_refresh_token == "oauth-refresh" + assert cred.oauth_access_token_expire == 1798790400 + assert cred.oauth_refresh_token_expire == 1801382400 + assert cred.region_id == "cn-hangzhou" + def test_load_ram_role_arn_from_config_file(self, tmp_path): config_file = tmp_path / "config.json" config = { @@ -408,6 +479,41 @@ def test_load_from_iac_code_config(self, tmp_path): assert cred.access_key_secret == "iac_secret" assert cred.region_id == "cn-beijing" + def test_load_oauth_from_iac_code_config(self, tmp_path): + cloud_creds_file = tmp_path / ".cloud-credentials.yml" + data = { + "aliyun": { + "mode": "OAuth", + "region_id": "cn-hangzhou", + "oauth_site_type": "CN", + "oauth_access_token": "oauth-access", + "oauth_refresh_token": "oauth-refresh", + "oauth_access_token_expire": 1798790400, + "oauth_refresh_token_expire": 1801382400, + "access_key_id": "tmp-ak", + "access_key_secret": "tmp-sk", + "sts_token": "tmp-sts", + "sts_expiration": 1798794000, + } + } + cloud_creds_file.write_text(yaml.dump(data)) + + with patch("iac_code.services.providers.aliyun.get_cloud_credentials_path", return_value=cloud_creds_file): + cred = AliyunCredentials._load_from_iac_code_config() + + assert cred is not None + assert cred.mode == "OAuth" + assert cred.region_id == "cn-hangzhou" + assert cred.oauth_site_type == "CN" + assert cred.oauth_access_token == "oauth-access" + assert cred.oauth_refresh_token == "oauth-refresh" + assert cred.oauth_access_token_expire == 1798790400 + assert cred.oauth_refresh_token_expire == 1801382400 + assert cred.access_key_id == "tmp-ak" + assert cred.access_key_secret == "tmp-sk" + assert cred.sts_token == "tmp-sts" + assert cred.sts_expiration == 1798794000 + def test_load_from_iac_code_returns_none_when_no_file(self, tmp_path): cloud_creds_file = tmp_path / ".cloud-credentials.yml" @@ -522,6 +628,40 @@ def test_save_ram_role_arn_to_iac_code_config(self, tmp_path): assert data["aliyun"]["ram_role_arn"] == "acs:ram::123:role/test" assert data["aliyun"]["ram_session_name"] == "session1" + def test_save_oauth_to_iac_code_config(self, tmp_path): + cloud_creds_file = tmp_path / ".cloud-credentials.yml" + + with patch("iac_code.services.providers.aliyun.get_cloud_credentials_path", return_value=cloud_creds_file): + cred = AliyunCredential( + mode="OAuth", + region_id="cn-hangzhou", + oauth_site_type="INTL", + oauth_access_token="oauth-access", + oauth_refresh_token="oauth-refresh", + oauth_access_token_expire=1798790400, + oauth_refresh_token_expire=1801382400, + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + sts_expiration=1798794000, + ) + AliyunCredentials.save(cred) + + data = yaml.safe_load(cloud_creds_file.read_text()) + assert data["aliyun"] == { + "mode": "OAuth", + "region_id": "cn-hangzhou", + "oauth_site_type": "INTL", + "oauth_access_token": "oauth-access", + "oauth_refresh_token": "oauth-refresh", + "oauth_access_token_expire": 1798790400, + "oauth_refresh_token_expire": 1801382400, + "access_key_id": "tmp-ak", + "access_key_secret": "tmp-sk", + "sts_token": "tmp-sts", + "sts_expiration": 1798794000, + } + def test_save_to_aliyun_cli_format(self, tmp_path): """Test save with config_path (aliyun CLI format, for testing).""" config_file = tmp_path / "config.json" @@ -612,6 +752,167 @@ def test_save_does_not_write_to_aliyun_cli_config(self, tmp_path): assert not aliyun_cli_file.exists() +class TestAliyunCredentialsOAuthRefresh: + def test_refresh_oauth_uses_unexpired_sts_without_network(self, monkeypatch): + cred = AliyunCredential( + mode="OAuth", + oauth_site_type="CN", + oauth_access_token="access", + oauth_refresh_token="refresh", + oauth_access_token_expire=2000, + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + sts_expiration=1900, + ) + + class FailingClient: + def refresh_access_token(self, refresh_token, *, now=None): + raise AssertionError("refresh_access_token should not be called") + + def exchange_access_token_for_sts(self, access_token): + raise AssertionError("exchange_access_token_for_sts should not be called") + + monkeypatch.setattr( + AliyunCredentials, + "save", + lambda credential: (_ for _ in ()).throw(AssertionError("save should not be called")), + ) + + refreshed = AliyunCredentials.refresh_oauth_if_needed(cred, oauth_client=FailingClient(), now=1000) + + assert refreshed is cred + assert cred.access_key_id == "tmp-ak" + assert cred.access_key_secret == "tmp-sk" + assert cred.sts_token == "tmp-sts" + assert cred.sts_expiration == 1900 + + def test_refresh_oauth_exchanges_expired_sts_with_current_access_token(self, monkeypatch): + cred = AliyunCredential( + mode="OAuth", + oauth_site_type="CN", + oauth_access_token="access", + oauth_refresh_token="refresh", + oauth_access_token_expire=2000, + access_key_id="old-ak", + access_key_secret="old-sk", + sts_token="old-sts", + sts_expiration=900, + ) + saved: list[AliyunCredential] = [] + + class FakeClient: + def exchange_access_token_for_sts(self, access_token): + assert access_token == "access" + return OAuthStsCredentials("new-ak", "new-sk", "new-sts", 2500) + + monkeypatch.setattr(AliyunCredentials, "save", saved.append) + + refreshed = AliyunCredentials.refresh_oauth_if_needed(cred, oauth_client=FakeClient(), now=1000) + + assert refreshed is cred + assert saved == [cred] + assert cred.access_key_id == "new-ak" + assert cred.access_key_secret == "new-sk" + assert cred.sts_token == "new-sts" + assert cred.sts_expiration == 2500 + + def test_refresh_oauth_refreshes_access_token_before_exchange(self, monkeypatch): + cred = AliyunCredential( + mode="OAuth", + oauth_site_type="CN", + oauth_access_token="old-access", + oauth_refresh_token="old-refresh", + oauth_access_token_expire=900, + access_key_id="old-ak", + access_key_secret="old-sk", + sts_token="old-sts", + sts_expiration=900, + ) + saved: list[AliyunCredential] = [] + + class FakeClient: + def refresh_access_token(self, refresh_token, *, now=None): + assert refresh_token == "old-refresh" + assert now == 1000 + return OAuthToken("new-access", "new-refresh", 4600, 0) + + def exchange_access_token_for_sts(self, access_token): + assert access_token == "new-access" + return OAuthStsCredentials("new-ak", "new-sk", "new-sts", 2500) + + monkeypatch.setattr(AliyunCredentials, "save", saved.append) + + refreshed = AliyunCredentials.refresh_oauth_if_needed(cred, oauth_client=FakeClient(), now=1000) + + assert refreshed is cred + assert saved == [cred] + assert cred.oauth_access_token == "new-access" + assert cred.oauth_refresh_token == "new-refresh" + assert cred.oauth_access_token_expire == 4600 + assert cred.oauth_refresh_token_expire == 0 + assert cred.access_key_id == "new-ak" + assert cred.access_key_secret == "new-sk" + assert cred.sts_token == "new-sts" + assert cred.sts_expiration == 2500 + + def test_refresh_oauth_requires_relogin_when_refresh_token_missing(self): + cred = AliyunCredential( + mode="OAuth", + oauth_site_type="CN", + oauth_access_token="old-access", + oauth_access_token_expire=900, + access_key_id="old-ak", + access_key_secret="old-sk", + sts_token="old-sts", + sts_expiration=900, + ) + + with pytest.raises(AliyunOAuthReloginRequired, match="/auth"): + AliyunCredentials.refresh_oauth_if_needed(cred, oauth_client=object(), now=1000) + + def test_refresh_oauth_returns_non_oauth_credentials_without_network(self, monkeypatch): + cred = AliyunCredential( + mode="AK", + access_key_id="ak", + access_key_secret="sk", + ) + + class FailingClient: + def refresh_access_token(self, refresh_token, *, now=None): + raise AssertionError("refresh_access_token should not be called") + + def exchange_access_token_for_sts(self, access_token): + raise AssertionError("exchange_access_token_for_sts should not be called") + + monkeypatch.setattr( + AliyunCredentials, + "save", + lambda credential: (_ for _ in ()).throw(AssertionError("save should not be called")), + ) + + refreshed = AliyunCredentials.refresh_oauth_if_needed(cred, oauth_client=FailingClient(), now=1000) + + assert refreshed is cred + assert cred.access_key_id == "ak" + assert cred.access_key_secret == "sk" + + def test_refresh_oauth_requires_relogin_when_oauth_site_type_missing(self): + cred = AliyunCredential( + mode="OAuth", + oauth_access_token="access", + oauth_refresh_token="refresh", + oauth_access_token_expire=2000, + access_key_id="old-ak", + access_key_secret="old-sk", + sts_token="old-sts", + sts_expiration=900, + ) + + with pytest.raises(AliyunOAuthReloginRequired, match="/auth"): + AliyunCredentials.refresh_oauth_if_needed(cred, oauth_client=object(), now=1000) + + class TestAliyunCredentialsIsConfigured: def test_is_configured_true_with_env_vars(self): env = { diff --git a/tests/services/providers/test_aliyun_oauth.py b/tests/services/providers/test_aliyun_oauth.py new file mode 100644 index 0000000..567faf8 --- /dev/null +++ b/tests/services/providers/test_aliyun_oauth.py @@ -0,0 +1,605 @@ +import socket +import threading +from urllib.error import HTTPError +from urllib.parse import parse_qs, urlparse +from urllib.request import urlopen + +import httpx +import pytest + +from iac_code.services.providers.aliyun_oauth import ( + AliyunOAuthCancelledError, + AliyunOAuthClient, + AliyunOAuthError, + AliyunOAuthReloginRequired, + OAuthCallbackServer, + OAuthStsCredentials, + OAuthToken, + build_authorization_url, + generate_code_challenge, + get_oauth_site, + is_epoch_expired, + parse_sts_exchange_response, + run_browser_oauth_flow, +) + + +def _free_loopback_port() -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(("127.0.0.1", 0)) + return int(sock.getsockname()[1]) + + +def test_get_cn_site_config(): + site = get_oauth_site("China") + + assert site.site_type == "CN" + assert site.display_name == "China" + assert site.client_id == "4038181954557748008" + assert site.signin_base_url == "https://signin.aliyun.com" + assert site.oauth_base_url == "https://oauth.aliyun.com" + + +def test_get_intl_site_config_accepts_international_alias(): + site = get_oauth_site("International") + + assert site.site_type == "INTL" + assert site.display_name == "International" + assert site.client_id == "4103531455503354461" + assert site.signin_base_url == "https://signin.alibabacloud.com" + assert site.oauth_base_url == "https://oauth.alibabacloud.com" + + +def test_generate_code_challenge_matches_rfc7636_example(): + verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + + assert generate_code_challenge(verifier) == "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" + + +def test_build_authorization_url_uses_signin_host_and_pkce(): + site = get_oauth_site("CN") + url = build_authorization_url( + site, + redirect_uri="http://127.0.0.1:12345/cli/callback", + state="fake-state", + code_challenge="fake-challenge", + ) + + parsed = urlparse(url) + query = parse_qs(parsed.query) + assert parsed.scheme == "https" + assert parsed.netloc == "signin.aliyun.com" + assert parsed.path == "/oauth2/v1/auth" + assert query == { + "response_type": ["code"], + "client_id": ["4038181954557748008"], + "redirect_uri": ["http://127.0.0.1:12345/cli/callback"], + "state": ["fake-state"], + "code_challenge": ["fake-challenge"], + "code_challenge_method": ["S256"], + } + + +def test_callback_server_accepts_matching_state(): + server = OAuthCallbackServer(ports=(_free_loopback_port(),), timeout_seconds=1) + server.start("expected-state") + + try: + with urlopen("{}?state=expected-state&code=auth-code".format(server.redirect_uri), timeout=1) as response: + body = response.read() + + assert response.status == 200 + assert b"Authorization successful" in body + assert server.wait_for_code() == "auth-code" + finally: + server.close() + + +def test_callback_server_rejects_invalid_state(): + server = OAuthCallbackServer(ports=(_free_loopback_port(),), timeout_seconds=1) + server.start("expected-state") + + try: + with pytest.raises(HTTPError) as exc_info: + urlopen("{}?state=wrong-state&code=auth-code".format(server.redirect_uri), timeout=1) + + assert exc_info.value.code == 400 + with pytest.raises(AliyunOAuthError, match="invalid state"): + server.wait_for_code() + finally: + server.close() + + +def test_callback_server_rejects_missing_code(): + server = OAuthCallbackServer(ports=(_free_loopback_port(),), timeout_seconds=1) + server.start("expected-state") + + try: + with pytest.raises(HTTPError) as exc_info: + urlopen("{}?state=expected-state".format(server.redirect_uri), timeout=1) + + assert exc_info.value.code == 400 + with pytest.raises(AliyunOAuthError, match="code not found"): + server.wait_for_code() + finally: + server.close() + + +def test_callback_server_timeout_includes_assignment_troubleshooting(): + server = OAuthCallbackServer(ports=(_free_loopback_port(),), timeout_seconds=0) + + with pytest.raises(AliyunOAuthError) as exc_info: + server.wait_for_code() + + message = str(exc_info.value) + assert "Timed out waiting for OAuth callback" in message + assert "official-cli" in message + assert "RAM user" in message + assert "RAM role" in message + assert "sign out" in message + assert "OAuth Login (Browser)" in message + + +def test_callback_server_wait_for_code_can_be_cancelled(): + server = OAuthCallbackServer(ports=(_free_loopback_port(),), timeout_seconds=30) + cancel_event = threading.Event() + cancel_event.set() + + with pytest.raises(AliyunOAuthCancelledError, match="cancelled"): + server.wait_for_code(cancel_event=cancel_event) + + +def test_run_browser_oauth_flow_prints_url_opens_browser_and_exchanges_code(): + opened_urls: list[str] = [] + lines: list[str] = [] + + class FakeServer: + redirect_uri = "http://127.0.0.1:12345/cli/callback" + + def start(self, expected_state: str) -> None: + assert expected_state + + def wait_for_code(self) -> str: + return "auth-code" + + def close(self) -> None: + pass + + class FakeClient: + def exchange_code_for_token( + self, + code: str, + redirect_uri: str, + code_verifier: str, + now: int | None = None, + ) -> OAuthToken: + assert code == "auth-code" + assert redirect_uri == FakeServer.redirect_uri + assert code_verifier + assert now == 1000 + return OAuthToken( + access_token="fake-access", + refresh_token="fake-refresh", + access_token_expire=4600, + ) + + def browser_opener(url: str) -> bool: + opened_urls.append(url) + return True + + token = run_browser_oauth_flow( + "CN", + oauth_client=FakeClient(), + browser_opener=browser_opener, + callback_server_factory=FakeServer, + writer=lines.append, + now=1000, + ) + + assert token.access_token == "fake-access" + assert token.refresh_token == "fake-refresh" + assert opened_urls + assert parse_qs(urlparse(opened_urls[0]).query)["redirect_uri"] == [FakeServer.redirect_uri] + assert lines[0] == "" + assert lines[1] == " Waiting for browser authorization" + assert lines[2].startswith(" 1. ") + assert lines[3].startswith(" 2. ") + assert lines[4].startswith(" 3. ") + assert lines[5].startswith(" 4. ") + assert "official-cli" in lines[2] + assert "RAM user" in lines[3] + assert "RAM role" in lines[3] + assert "User groups are not supported" in lines[3] + assert "Press Esc" in lines[6] + assert lines[-2] == " Open in your browser:" + assert lines[-1].startswith(" https://signin.aliyun.com/oauth2/v1/auth?") + assert not any("SignIn url" in line for line in lines) + + +def test_run_browser_oauth_flow_passes_cancel_event_to_callback_wait(): + cancel_event = threading.Event() + + class FakeServer: + redirect_uri = "http://127.0.0.1:12345/cli/callback" + + def start(self, expected_state: str) -> None: + assert expected_state + + def wait_for_code(self, *, cancel_event: threading.Event | None = None) -> str: + assert cancel_event is not None + assert cancel_event is expected_cancel_event + return "auth-code" + + def close(self) -> None: + pass + + expected_cancel_event = cancel_event + + class FakeClient: + def exchange_code_for_token( + self, + code: str, + redirect_uri: str, + code_verifier: str, + now: int | None = None, + ) -> OAuthToken: + return OAuthToken( + access_token="fake-access", + refresh_token="fake-refresh", + access_token_expire=4600, + ) + + token = run_browser_oauth_flow( + "CN", + oauth_client=FakeClient(), + browser_opener=lambda url: True, + callback_server_factory=FakeServer, + writer=lambda line: None, + cancel_event=cancel_event, + ) + + assert token.access_token == "fake-access" + + +def test_run_browser_oauth_flow_continues_when_browser_open_raises(): + class FakeServer: + redirect_uri = "http://127.0.0.1:12345/cli/callback" + + def start(self, expected_state: str) -> None: + assert expected_state + + def wait_for_code(self) -> str: + return "auth-code" + + def close(self) -> None: + pass + + class FakeClient: + def exchange_code_for_token( + self, + code: str, + redirect_uri: str, + code_verifier: str, + now: int | None = None, + ) -> OAuthToken: + assert code == "auth-code" + assert redirect_uri == FakeServer.redirect_uri + assert code_verifier + return OAuthToken( + access_token="fake-access", + refresh_token="fake-refresh", + access_token_expire=4600, + ) + + def browser_opener(url: str) -> bool: + raise RuntimeError("browser unavailable") + + token = run_browser_oauth_flow( + "CN", + oauth_client=FakeClient(), + browser_opener=browser_opener, + callback_server_factory=FakeServer, + writer=lambda line: None, + ) + + assert token.access_token == "fake-access" + + +def test_parse_sts_exchange_response_accepts_camel_case(): + credentials = parse_sts_exchange_response( + { + "accessKeyId": "fake-ak", + "accessKeySecret": "fake-secret", + "securityToken": "fake-sts", + "expiration": "2026-01-01T01:00:00Z", + } + ) + + assert credentials == OAuthStsCredentials( + access_key_id="fake-ak", + access_key_secret="fake-secret", + sts_token="fake-sts", + sts_expiration=1767229200, + ) + + +def test_parse_sts_exchange_response_accepts_pascal_case(): + credentials = parse_sts_exchange_response( + { + "AccessKeyId": "fake-ak", + "AccessKeySecret": "fake-secret", + "SecurityToken": "fake-sts", + "Expiration": "1767229200", + } + ) + + assert credentials == OAuthStsCredentials( + access_key_id="fake-ak", + access_key_secret="fake-secret", + sts_token="fake-sts", + sts_expiration=1767229200, + ) + + +def test_is_epoch_expired_uses_skew(): + assert is_epoch_expired(1061, now=1000, skew_seconds=60) is False + assert is_epoch_expired(1060, now=1000, skew_seconds=60) is True + assert is_epoch_expired(0, now=1000, skew_seconds=60) is True + + +def test_exchange_code_for_token_posts_authorization_code_form(): + requests: list[httpx.Request] = [] + + def handler(request: httpx.Request) -> httpx.Response: + requests.append(request) + return httpx.Response( + 200, + json={ + "access_token": "fake-access", + "refresh_token": "fake-refresh", + "expires_in": 3600, + "refresh_expires_in": 86400, + }, + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + token = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client).exchange_code_for_token( + code="fake-code", + redirect_uri="http://127.0.0.1:12345/cli/callback", + code_verifier="fake-verifier", + now=1000, + ) + + assert token.access_token == "fake-access" + assert token.refresh_token == "fake-refresh" + assert token.access_token_expire == 4600 + assert token.refresh_token_expire == 87400 + assert requests[0].method == "POST" + assert str(requests[0].url) == "https://oauth.aliyun.com/v1/token" + assert parse_qs(requests[0].content.decode()) == { + "grant_type": ["authorization_code"], + "code": ["fake-code"], + "client_id": ["4038181954557748008"], + "redirect_uri": ["http://127.0.0.1:12345/cli/callback"], + "code_verifier": ["fake-verifier"], + } + + +def test_refresh_access_token_preserves_existing_refresh_token_when_response_omits_it(): + def handler(request: httpx.Request) -> httpx.Response: + assert parse_qs(request.content.decode()) == { + "grant_type": ["refresh_token"], + "refresh_token": ["existing-refresh"], + "client_id": ["4038181954557748008"], + } + return httpx.Response( + 200, + json={ + "access_token": "new-access", + "expires_in": 1800, + }, + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + token = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client).refresh_access_token( + "existing-refresh", + now=2000, + ) + + assert token.access_token == "new-access" + assert token.refresh_token == "existing-refresh" + assert token.access_token_expire == 3800 + assert token.refresh_token_expire == 0 + + +def test_exchange_access_token_for_sts_sends_bearer_header(): + def handler(request: httpx.Request) -> httpx.Response: + assert request.headers["authorization"] == "Bearer fake-access" + assert request.headers["content-type"] == "application/json" + return httpx.Response( + 200, + json={ + "accessKeyId": "fake-ak", + "accessKeySecret": "fake-secret", + "securityToken": "fake-sts", + "expiration": 1767229200, + }, + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + credentials = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client).exchange_access_token_for_sts( + "fake-access" + ) + + assert credentials == OAuthStsCredentials( + access_key_id="fake-ak", + access_key_secret="fake-secret", + sts_token="fake-sts", + sts_expiration=1767229200, + ) + + +def test_permanent_oauth_error_raises_relogin_required(): + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response( + 400, + json={"error": "invalid_grant", "error_description": "authorization code expired"}, + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + client = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client) + with pytest.raises(AliyunOAuthReloginRequired) as exc_info: + client.refresh_access_token("fake-refresh") + + assert exc_info.value.error_code == "invalid_grant" + assert exc_info.value.status_code == 400 + assert "refresh access token failed with status 400" in str(exc_info.value) + assert "invalid_grant" in str(exc_info.value) + assert "authorization code expired" in str(exc_info.value) + assert "/auth" in str(exc_info.value) + assert "OAuth Login (Browser)" in str(exc_info.value) + assert "fake-refresh" not in str(exc_info.value) + + +def test_refresh_access_token_redacts_refresh_token_from_error_description(): + refresh_token = "refresh-token-secret" + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response( + 400, + json={ + "error": "invalid_grant", + "error_description": "refresh token refresh-token-secret is expired", + }, + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + client = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client) + with pytest.raises(AliyunOAuthReloginRequired) as exc_info: + client.refresh_access_token(refresh_token) + + message = str(exc_info.value) + assert "[REDACTED]" in message + assert refresh_token not in message + assert "refresh token" in message + assert "is expired" in message + assert "/auth" in message + assert "OAuth Login (Browser)" in message + + +def test_exchange_access_token_for_sts_redacts_access_token_from_error_description(): + access_token = "access-token-secret" + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response( + 500, + json={ + "error": "server_error", + "error_description": "bearer access-token-secret failed validation", + }, + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + client = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client) + with pytest.raises(AliyunOAuthError) as exc_info: + client.exchange_access_token_for_sts(access_token) + + message = str(exc_info.value) + assert "[REDACTED]" in message + assert access_token not in message + assert "bearer" in message + assert "failed validation" in message + + +def test_non_permanent_oauth_error_raises_oauth_error(): + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response( + 500, + json={"error": "server_error", "error_description": "temporary outage"}, + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + client = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client) + with pytest.raises(AliyunOAuthError) as exc_info: + client.exchange_access_token_for_sts("fake-access") + + assert not isinstance(exc_info.value, AliyunOAuthReloginRequired) + assert exc_info.value.error_code == "server_error" + assert exc_info.value.status_code == 500 + assert "exchange access token for STS failed with status 500" in str(exc_info.value) + assert "server_error" in str(exc_info.value) + assert "temporary outage" in str(exc_info.value) + assert "fake-access" not in str(exc_info.value) + + +def test_exchange_code_for_token_wraps_http_error_and_redacts_sensitive_values(): + auth_code = "auth-code-secret" + code_verifier = "verifier-secret" + + def handler(request: httpx.Request) -> httpx.Response: + raise httpx.ConnectError( + "connection failed for auth-code-secret with verifier-secret", + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + client = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client) + with pytest.raises(AliyunOAuthError) as exc_info: + client.exchange_code_for_token( + code=auth_code, + redirect_uri="http://127.0.0.1:12345/cli/callback", + code_verifier=code_verifier, + ) + + message = str(exc_info.value) + assert "exchange authorization code for token request failed" in message + assert "[REDACTED]" in message + assert auth_code not in message + assert code_verifier not in message + + +def test_refresh_access_token_wraps_http_error_and_redacts_refresh_token(): + refresh_token = "refresh-token-secret" + + def handler(request: httpx.Request) -> httpx.Response: + raise httpx.TimeoutException( + "timeout while using refresh-token-secret", + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + client = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client) + with pytest.raises(AliyunOAuthError) as exc_info: + client.refresh_access_token(refresh_token) + + message = str(exc_info.value) + assert "refresh access token request failed" in message + assert "[REDACTED]" in message + assert refresh_token not in message + + +def test_exchange_access_token_for_sts_wraps_http_error_and_redacts_access_token(): + access_token = "access-token-secret" + + def handler(request: httpx.Request) -> httpx.Response: + raise httpx.ConnectError( + "connection failed for access-token-secret", + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + client = AliyunOAuthClient(get_oauth_site("CN"), http_client=http_client) + with pytest.raises(AliyunOAuthError) as exc_info: + client.exchange_access_token_for_sts(access_token) + + message = str(exc_info.value) + assert "exchange access token for STS request failed" in message + assert "[REDACTED]" in message + assert access_token not in message diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 3d859a8..4287fa4 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -327,6 +327,31 @@ def test_memory_command_translations_are_complete(): assert not errors, "\n".join(errors) +@pytest.mark.skipif(sys.platform == "win32", reason="messages.pot not generated on Windows") +def test_aliyun_credential_labels_are_translatable(): + """Aliyun auth menu labels come from data tables, so guard against dynamic gettext misses.""" + from iac_code.services.providers.aliyun import MODE_DISPLAY_NAMES, MODE_FIELDS + + required_msgids = set(MODE_DISPLAY_NAMES.values()) + for mode_fields in MODE_FIELDS.values(): + required_msgids.update(label for _field_name, label, _sensitive in mode_fields) + + pot_msgids = _get_all_msgids_from_pot(POT_FILE) + missing_from_pot = sorted(required_msgids - pot_msgids) + assert not missing_from_pot, "Aliyun credential labels missing from messages.pot: {}".format(missing_from_pot) + + missing_or_empty_by_language: dict[str, list[str]] = {} + for lang_dir in _discover_language_dirs(): + translations = _get_all_translations_from_po(lang_dir / "LC_MESSAGES" / "messages.po") + missing_or_empty = sorted(msgid for msgid in required_msgids if not translations.get(msgid)) + if missing_or_empty: + missing_or_empty_by_language[lang_dir.name] = missing_or_empty + + assert not missing_or_empty_by_language, "Aliyun credential labels missing translations: {}".format( + missing_or_empty_by_language + ) + + class TestDetectWindowsUILanguage: """_detect_windows_ui_language wraps GetUserDefaultLocaleName via ctypes.""" diff --git a/tests/tools/cloud/aliyun/test_aliyun_api.py b/tests/tools/cloud/aliyun/test_aliyun_api.py index e8c897a..af4cd38 100644 --- a/tests/tools/cloud/aliyun/test_aliyun_api.py +++ b/tests/tools/cloud/aliyun/test_aliyun_api.py @@ -8,6 +8,7 @@ import pytest from iac_code.services.providers.aliyun import AliyunCredential +from iac_code.services.providers.aliyun_oauth import AliyunOAuthError, AliyunOAuthReloginRequired from iac_code.tools.base import ToolContext from iac_code.tools.cloud.aliyun import aliyun_api as aliyun_api_module from iac_code.tools.cloud.aliyun.aliyun_api import AliyunApi @@ -424,6 +425,122 @@ async def test_params_serialized_in_request(self, api: AliyunApi, context: ToolC assert request.query["PageSize"] == "10" assert request.query["DryRun"] == "true" + @pytest.mark.asyncio + async def test_execute_refreshes_oauth_before_endpoint_discovery( + self, api: AliyunApi, context: ToolContext + ) -> None: + oauth_cred = AliyunCredential( + mode="OAuth", + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + region_id="cn-hangzhou", + oauth_access_token="access-token", + oauth_refresh_token="refresh-token", + ) + refreshed = AliyunCredential( + mode="OAuth", + access_key_id="new-ak", + access_key_secret="new-sk", + sts_token="new-sts", + region_id="cn-hangzhou", + oauth_access_token="access-token", + oauth_refresh_token="refresh-token", + ) + mock_client = MagicMock() + mock_client.call_api.side_effect = [ + {"body": {"Endpoints": {"Endpoint": [{"Type": "openAPI", "Endpoint": "custom.aliyuncs.com"}]}}}, + {"body": {"Instances": []}}, + ] + + with ( + patch("iac_code.tools.cloud.aliyun.aliyun_api.CloudCredentials") as cloud_credentials, + patch.object( + aliyun_api_module.AliyunCredentials, "refresh_oauth_if_needed", return_value=refreshed + ) as refresh, + patch("iac_code.tools.cloud.aliyun.aliyun_api.OpenApiClient", return_value=mock_client) as client_cls, + ): + cloud_credentials.return_value.get_provider.return_value = oauth_cred + result = await api.execute( + tool_input={ + "product": "custom-svc", + "action": "DescribeInstances", + "version": "2023-01-01", + "region_id": "cn-hangzhou", + }, + context=context, + ) + + assert result.is_error is False + refresh.assert_called_once_with(oauth_cred) + discovery_config = client_cls.call_args_list[0].args[0] + call_config = client_cls.call_args_list[1].args[0] + assert discovery_config.access_key_id == "new-ak" + assert discovery_config.security_token == "new-sts" + assert call_config.access_key_id == "new-ak" + assert call_config.security_token == "new-sts" + + @pytest.mark.asyncio + async def test_execute_returns_relogin_error_when_oauth_refresh_requires_login( + self, api: AliyunApi, context: ToolContext + ) -> None: + oauth_cred = AliyunCredential( + mode="OAuth", + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + region_id="cn-hangzhou", + oauth_access_token="access-token", + oauth_refresh_token="refresh-token", + ) + + with ( + patch("iac_code.tools.cloud.aliyun.aliyun_api.CloudCredentials") as cloud_credentials, + patch.object( + aliyun_api_module.AliyunCredentials, + "refresh_oauth_if_needed", + side_effect=AliyunOAuthReloginRequired("Run /auth and choose OAuth Login (Browser)."), + ), + ): + cloud_credentials.return_value.get_provider.return_value = oauth_cred + result = await api.execute( + tool_input={"product": "ecs", "action": "DescribeInstances", "region_id": "cn-hangzhou"}, + context=context, + ) + + assert result.is_error is True + assert "/auth" in result.content + assert "OAuth Login (Browser)" in result.content + + @pytest.mark.asyncio + async def test_execute_returns_oauth_error_when_refresh_fails(self, api: AliyunApi, context: ToolContext) -> None: + oauth_cred = AliyunCredential( + mode="OAuth", + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + region_id="cn-hangzhou", + oauth_access_token="access-token", + oauth_refresh_token="refresh-token", + ) + + with ( + patch("iac_code.tools.cloud.aliyun.aliyun_api.CloudCredentials") as cloud_credentials, + patch.object( + aliyun_api_module.AliyunCredentials, + "refresh_oauth_if_needed", + side_effect=AliyunOAuthError("temporary oauth refresh failure"), + ), + ): + cloud_credentials.return_value.get_provider.return_value = oauth_cred + result = await api.execute( + tool_input={"product": "ecs", "action": "DescribeInstances", "region_id": "cn-hangzhou"}, + context=context, + ) + + assert result.is_error is True + assert "temporary oauth refresh failure" in result.content + class TestAliyunApiProductNormalization: @pytest.mark.asyncio @@ -527,6 +644,22 @@ def test_sts_token_mode(self) -> None: assert config.region_id == "cn-beijing" assert config.user_agent and config.user_agent.startswith("iac-code/") + def test_oauth_mode_builds_sts_config(self) -> None: + credential = AliyunCredential( + mode="OAuth", + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + region_id="cn-hangzhou", + ) + config = AliyunApi._build_config(credential, "ecs.aliyuncs.com", "cn-hangzhou") + assert config.access_key_id == "tmp-ak" + assert config.access_key_secret == "tmp-sk" + assert config.security_token == "tmp-sts" + assert config.endpoint == "ecs.aliyuncs.com" + assert config.region_id == "cn-hangzhou" + assert config.user_agent and config.user_agent.startswith("iac-code/") + def test_ram_role_arn_mode(self) -> None: credential = AliyunCredential( mode="RamRoleArn", diff --git a/tests/tools/cloud/aliyun/test_ros_client.py b/tests/tools/cloud/aliyun/test_ros_client.py index dc77daa..2870e5d 100644 --- a/tests/tools/cloud/aliyun/test_ros_client.py +++ b/tests/tools/cloud/aliyun/test_ros_client.py @@ -68,6 +68,61 @@ def test_sts_token_mode_builds_config(self): assert config.region_id == "cn-hangzhou" assert config.user_agent and config.user_agent.startswith("iac-code/") + def test_oauth_mode_builds_sts_config(self): + from iac_code.services.providers.aliyun import AliyunCredential + from iac_code.tools.cloud.aliyun.ros_client import RosClientFactory + + cred = AliyunCredential( + mode="OAuth", + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + region_id="cn-hangzhou", + ) + config = RosClientFactory._build_config(cred, "cn-hangzhou") + assert config.access_key_id == "tmp-ak" + assert config.access_key_secret == "tmp-sk" + assert config.security_token == "tmp-sts" + assert config.region_id == "cn-hangzhou" + assert config.user_agent and config.user_agent.startswith("iac-code/") + + def test_create_refreshes_oauth_before_building_client(self): + from unittest.mock import patch + + from iac_code.services.providers.aliyun import AliyunCredential + from iac_code.tools.cloud.aliyun import ros_client + + oauth_cred = AliyunCredential( + mode="OAuth", + access_key_id="tmp-ak", + access_key_secret="tmp-sk", + sts_token="tmp-sts", + region_id="cn-hangzhou", + oauth_access_token="access-token", + oauth_refresh_token="refresh-token", + ) + refreshed = AliyunCredential( + mode="OAuth", + access_key_id="new-ak", + access_key_secret="new-sk", + sts_token="new-sts", + region_id="cn-hangzhou", + oauth_access_token="access-token", + oauth_refresh_token="refresh-token", + ) + + with ( + patch.object(ros_client.AliyunCredentials, "refresh_oauth_if_needed", return_value=refreshed) as refresh, + patch.object(ros_client, "RosClient") as client_cls, + ): + ros_client.RosClientFactory.create(oauth_cred) + + refresh.assert_called_once_with(oauth_cred) + config = client_cls.call_args.args[0] + assert config.access_key_id == "new-ak" + assert config.access_key_secret == "new-sk" + assert config.security_token == "new-sts" + def test_ram_role_arn_mode_builds_config(self): from iac_code.services.providers.aliyun import AliyunCredential from iac_code.tools.cloud.aliyun.ros_client import RosClientFactory From cea3c08a88ef26bf29cb79a3ac72fb7e4e5020c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 11:01:38 +0800 Subject: [PATCH 05/11] feat: add named session resume and rename --- src/iac_code/acp/server.py | 133 ++- src/iac_code/acp/slash_registry.py | 32 +- src/iac_code/cli/main.py | 2 +- src/iac_code/commands/__init__.py | 14 +- src/iac_code/commands/clear.py | 12 +- src/iac_code/commands/registry.py | 12 + src/iac_code/commands/rename.py | 43 + src/iac_code/commands/resume.py | 29 +- .../i18n/locales/de/LC_MESSAGES/messages.po | 789 +++++++++++------ .../i18n/locales/es/LC_MESSAGES/messages.po | 801 +++++++++++------ .../i18n/locales/fr/LC_MESSAGES/messages.po | 805 ++++++++++++------ .../i18n/locales/ja/LC_MESSAGES/messages.po | 653 +++++++++----- .../i18n/locales/pt/LC_MESSAGES/messages.po | 761 +++++++++++------ .../i18n/locales/zh/LC_MESSAGES/messages.po | 592 ++++++++----- src/iac_code/services/session_index.py | 78 +- src/iac_code/services/session_metadata.py | 78 ++ src/iac_code/services/session_resolver.py | 73 ++ src/iac_code/services/session_storage.py | 116 ++- src/iac_code/ui/banner.py | 17 +- src/iac_code/ui/dialogs/resume_picker.py | 27 +- src/iac_code/ui/repl.py | 165 +++- tests/acp/test_mcp.py | 20 + tests/acp/test_scenarios.py | 4 + tests/acp/test_server_coverage.py | 19 + tests/acp/test_sessions.py | 168 +++- tests/acp/test_slash_registry.py | 75 ++ tests/commands/test_clear.py | 32 +- tests/commands/test_registry.py | 15 +- tests/commands/test_rename.py | 120 +++ tests/commands/test_resume.py | 107 ++- tests/services/test_session_index.py | 72 ++ tests/services/test_session_metadata.py | 77 ++ tests/services/test_session_resolver.py | 74 ++ tests/services/test_session_storage.py | 67 +- tests/test_i18n.py | 22 + tests/ui/dialogs/test_resume_picker.py | 88 +- tests/ui/test_banner.py | 11 +- tests/ui/test_repl_integration.py | 341 +++++++- tests/ui/test_repl_shell_escape.py | 37 + 39 files changed, 4987 insertions(+), 1594 deletions(-) create mode 100644 src/iac_code/commands/rename.py create mode 100644 src/iac_code/services/session_metadata.py create mode 100644 src/iac_code/services/session_resolver.py create mode 100644 tests/commands/test_rename.py create mode 100644 tests/services/test_session_metadata.py create mode 100644 tests/services/test_session_resolver.py diff --git a/src/iac_code/acp/server.py b/src/iac_code/acp/server.py index 729e4a4..7bebdf1 100644 --- a/src/iac_code/acp/server.py +++ b/src/iac_code/acp/server.py @@ -3,6 +3,7 @@ import asyncio import contextlib import logging +import shlex import time import uuid from typing import Any @@ -18,7 +19,10 @@ from iac_code.acp.version import negotiate_version from iac_code.commands import LocalCommand, create_default_registry from iac_code.config import DEFAULT_MODEL, get_active_provider_key, load_saved_model +from iac_code.i18n import _ from iac_code.services.agent_factory import AgentFactoryOptions, create_agent_runtime +from iac_code.services.session_index import SessionEntry, SessionIndex +from iac_code.services.session_resolver import ResolutionStatus, resolve_session_argument from iac_code.services.session_storage import SessionStorage SESSION_IDLE_TIMEOUT = 3600 # 1 hour @@ -233,25 +237,16 @@ async def list_sessions( cwd: str | None = None, **kwargs: Any, ) -> acp.schema.ListSessionsResponse: - from iac_code.utils.project_paths import get_project_dir, get_projects_dir - - session_ids: list[str] = [] - if cwd: - project_dir = get_project_dir(cwd) - if project_dir.exists(): - session_ids = [p.stem for p in project_dir.glob("*.jsonl")] - else: - projects_root = get_projects_dir() - if projects_root.exists(): - session_ids = [p.stem for p in projects_root.glob("*/*.jsonl")] + index = SessionIndex() + entries = index.list_for_cwd(cwd) if cwd else index.list_all_projects() return acp.schema.ListSessionsResponse( sessions=[ acp.schema.SessionInfo( - session_id=session_id, - cwd=cwd or "", - title=session_id, + session_id=entry.session_id, + cwd=entry.cwd or cwd or "", + title=entry.title, ) - for session_id in session_ids + for entry in entries ], next_cursor=None, ) @@ -401,20 +396,66 @@ async def resume_session( mcp_servers: list[MCPServer] | None = None, **kwargs: Any, ) -> acp.schema.ResumeSessionResponse: - # 1. If session is still active in memory, return directly - if session_id in self.sessions: + # 1. If session is still active in memory by exact id, enforce project ownership before returning. + active_session = self.sessions.get(session_id) + if active_session is not None: + error = _active_session_project_error(cwd, session_id, session_id, active_session) + if error is not None: + raise error await self._push_available_commands(session_id) return acp.schema.ResumeSessionResponse() if self.conn is None: raise acp.RequestError.internal_error({"error": "ACP client not connected"}) - # 2. Try to load persisted history from SessionStorage + resolution = resolve_session_argument(SessionIndex(), cwd, session_id) + if resolution.status == ResolutionStatus.NOT_FOUND: + raise _invalid_params(_("Session not found"), {"session_id": session_id}) + if resolution.status == ResolutionStatus.AMBIGUOUS_NAME: + candidate_ids = [entry.session_id for entry in resolution.candidates] + message = _("Session name is ambiguous. Candidates: {candidates}").format( + candidates=", ".join(candidate_ids) + ) + raise _invalid_params( + message, + { + "session_id": session_id, + "candidates": [_resume_candidate_data(entry) for entry in resolution.candidates], + }, + ) + + entry = resolution.entry + if entry is None: # pragma: no cover - defensive guard for inconsistent resolver output + raise _invalid_params(_("Session not found"), {"session_id": session_id}) + + resolved_session_id = entry.session_id + if entry.cwd and entry.cwd != cwd: + hint = _resume_command(entry.cwd, resolved_session_id) + message = _("Session belongs to another project. Run: {hint}").format(hint=hint) + raise _invalid_params( + message, + { + "session_id": session_id, + "resolved_session_id": resolved_session_id, + "cwd": entry.cwd, + "hint": hint, + }, + ) + + active_session = self.sessions.get(resolved_session_id) + if active_session is not None: + error = _active_session_project_error(cwd, session_id, resolved_session_id, active_session) + if error is not None: + raise error + await self._push_available_commands(resolved_session_id) + return acp.schema.ResumeSessionResponse() + + # 2. Try to load persisted history from SessionStorage. storage = SessionStorage() - if not storage.exists(cwd, session_id): - raise acp.RequestError.invalid_params({"session_id": "Session not found"}) + if not storage.exists(cwd, resolved_session_id): + raise _invalid_params(_("Session not found"), {"session_id": session_id}) - history = storage.load(cwd, session_id) + history = storage.load(cwd, resolved_session_id) history = SessionStorage.repair_interrupted(history) # Convert MCP server configs from ACP protocol types to internal dicts @@ -422,7 +463,7 @@ async def resume_session( # 3. Rebuild agent runtime with restored history model = load_saved_model() or DEFAULT_MODEL - runtime = self._create_runtime_with_auth_check(model=model, session_id=session_id, cwd=cwd) + runtime = self._create_runtime_with_auth_check(model=model, session_id=resolved_session_id, cwd=cwd) replace_bash_with_acp_terminal( runtime.tool_registry, self.client_capabilities, @@ -436,16 +477,16 @@ async def resume_session( # 4. Register the resumed session session = ACPSession( - session_id, + resolved_session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics, memory_manager=getattr(runtime, "memory_manager", None), ) - self.sessions[session_id] = session + self.sessions[resolved_session_id] = session self.metrics.record_session_created() - await self._push_available_commands(session_id) + await self._push_available_commands(resolved_session_id) return acp.schema.ResumeSessionResponse() @@ -643,6 +684,48 @@ def _convert_mcp_servers(mcp_servers: list[MCPServer] | None) -> list[dict[str, return configs +def _invalid_params(message: str, data: dict[str, Any] | None = None) -> acp.RequestError: + """Create an ACP invalid-params error with a useful message.""" + return acp.RequestError(-32602, message, data) + + +def _resume_command(cwd: str, session_id: str) -> str: + return "cd {cwd} && iac-code --resume {session_id}".format(cwd=shlex.quote(cwd), session_id=session_id) + + +def _active_session_cwd(session: ACPSession) -> str | None: + cwd = getattr(session.agent_loop, "_cwd", None) + return cwd if isinstance(cwd, str) and cwd else None + + +def _active_session_project_error( + cwd: str, session_id: str, resolved_session_id: str, session: ACPSession +) -> acp.RequestError | None: + active_cwd = _active_session_cwd(session) + if not active_cwd or active_cwd == cwd: + return None + hint = _resume_command(active_cwd, resolved_session_id) + message = _("Session belongs to another project. Run: {hint}").format(hint=hint) + return _invalid_params( + message, + { + "session_id": session_id, + "resolved_session_id": resolved_session_id, + "cwd": active_cwd, + "hint": hint, + }, + ) + + +def _resume_candidate_data(entry: SessionEntry) -> dict[str, str | None]: + return { + "session_id": entry.session_id, + "name": entry.name, + "cwd": entry.cwd, + "command": _resume_command(entry.cwd, entry.session_id), + } + + # --------------------------------------------------------------------------- # Auth methods declaration # --------------------------------------------------------------------------- diff --git a/src/iac_code/acp/slash_registry.py b/src/iac_code/acp/slash_registry.py index 1646553..bdcdc50 100644 --- a/src/iac_code/acp/slash_registry.py +++ b/src/iac_code/acp/slash_registry.py @@ -1,7 +1,7 @@ """ACP slash command registry. Manages commands supported over the ACP protocol. -Only /compact, /clear, /debug, and /memory are allowed; +Only /compact, /clear, /debug, /memory, and /rename are allowed; all other slash commands are rejected with a clear message. """ @@ -10,10 +10,12 @@ import logging from iac_code.i18n import _ +from iac_code.services.session_metadata import normalize_session_name +from iac_code.services.session_storage import SessionStorage logger = logging.getLogger(__name__) -ACP_SUPPORTED_COMMANDS: frozenset[str] = frozenset({"compact", "clear", "debug", "memory"}) +ACP_SUPPORTED_COMMANDS: frozenset[str] = frozenset({"compact", "clear", "debug", "memory", "rename"}) class ACPSlashRegistry: @@ -53,6 +55,8 @@ async def execute(self, text: str, agent_loop, **context) -> str: return self._handle_debug(args_str) if cmd_name == "memory": return self._handle_memory(args_str, context.get("memory_manager")) + if cmd_name == "rename": + return self._handle_rename(args_str, agent_loop) # Should not reach here return _("Command '/{cmd_name}' handler not implemented.").format(cmd_name=cmd_name) # pragma: no cover @@ -134,3 +138,27 @@ def _handle_memory(self, args: str, memory_manager) -> str: from iac_code.commands.memory import execute_memory_command return execute_memory_command(memory_manager, args.split()) + + def _handle_rename(self, args: str, agent_loop) -> str: + """Rename the current ACP session non-interactively.""" + parts = args.split() + if len(parts) != 1: + return _("Usage: /rename ") + + cwd = getattr(agent_loop, "_cwd", None) + session_id = getattr(agent_loop, "_session_id", None) + git_branch = getattr(agent_loop, "_current_git_branch", None) + if not isinstance(cwd, str) or not isinstance(session_id, str): + return _("Rename is only available after a session is created.") + if not isinstance(git_branch, str): + git_branch = None + + try: + name = normalize_session_name(parts[0]) + result = SessionStorage().rename_session(cwd, session_id, name, git_branch=git_branch) + except ValueError as exc: + return str(exc) + + if result == "unchanged": + return _("Session is already named {name}").format(name=name) + return _("Renamed session to {name}").format(name=name) diff --git a/src/iac_code/cli/main.py b/src/iac_code/cli/main.py index 060b8c1..8879c36 100644 --- a/src/iac_code/cli/main.py +++ b/src/iac_code/cli/main.py @@ -86,7 +86,7 @@ def main( debug: bool = typer.Option(False, "--debug", "-d", help=_("Enable debug logging")), verbose: bool = typer.Option(False, "--verbose", help=_("Show headless progress on stderr")), version: bool = typer.Option(False, "--version", "-v", "-V", is_eager=True, help=_("Show version and exit")), - resume: str = typer.Option("", "--resume", "-r", help=_("Resume a session by ID")), + resume: str = typer.Option("", "--resume", "-r", help=_("Resume a session by ID or name")), continue_session: bool = typer.Option(False, "--continue", "-c", help=_("Resume the most recent session")), install_completion: bool = typer.Option( None, diff --git a/src/iac_code/commands/__init__.py b/src/iac_code/commands/__init__.py index ed86a12..0ef1557 100644 --- a/src/iac_code/commands/__init__.py +++ b/src/iac_code/commands/__init__.py @@ -9,7 +9,8 @@ from iac_code.commands.help import help_command from iac_code.commands.memory import memory_command from iac_code.commands.model import model_command -from iac_code.commands.registry import Command, CommandRegistry, LocalCommand, PromptCommand +from iac_code.commands.registry import Command, CommandRegistry, CommandResult, LocalCommand, PromptCommand +from iac_code.commands.rename import rename_command from iac_code.commands.resume import resume_command from iac_code.commands.skills import skills_command from iac_code.commands.status import status_command @@ -108,6 +109,15 @@ def create_default_registry() -> CommandRegistry: history_mode="session", ) ) + registry.register( + LocalCommand( + name="rename", + description=_("Rename the current session"), + handler=rename_command, + arg_hint="", + history_mode="session", + ) + ) registry.register( LocalCommand( name="skills", @@ -127,4 +137,4 @@ def create_default_registry() -> CommandRegistry: return registry -__all__ = ["Command", "CommandRegistry", "LocalCommand", "PromptCommand", "create_default_registry"] +__all__ = ["Command", "CommandRegistry", "CommandResult", "LocalCommand", "PromptCommand", "create_default_registry"] diff --git a/src/iac_code/commands/clear.py b/src/iac_code/commands/clear.py index 541ef68..1a63c43 100644 --- a/src/iac_code/commands/clear.py +++ b/src/iac_code/commands/clear.py @@ -31,6 +31,16 @@ async def clear_command(context=None, **kwargs) -> str: state = store.get_state() if store else None if state: - console.print(render_welcome_banner(state.model, state.cwd)) + repl = getattr(context, "repl", None) + session_id = getattr(repl, "_session_id", None) + session_name = getattr(repl, "_session_name", None) + console.print( + render_welcome_banner( + state.model, + state.cwd, + session_id=session_id if isinstance(session_id, str) else None, + session_name=session_name if isinstance(session_name, str) else None, + ) + ) return "" diff --git a/src/iac_code/commands/registry.py b/src/iac_code/commands/registry.py index 001555a..1d15115 100644 --- a/src/iac_code/commands/registry.py +++ b/src/iac_code/commands/registry.py @@ -50,6 +50,18 @@ class LocalCommand(Command): """ +@dataclass(frozen=True) +class CommandResult: + """Structured result for local commands that need UI metadata.""" + + message: str + is_error: bool = False + refresh_banner: bool = False + + def __bool__(self) -> bool: + return bool(self.message) + + @dataclass class PromptCommand(Command): """Skill-based command backed by a SkillDefinition. diff --git a/src/iac_code/commands/rename.py b/src/iac_code/commands/rename.py new file mode 100644 index 0000000..a878585 --- /dev/null +++ b/src/iac_code/commands/rename.py @@ -0,0 +1,43 @@ +"""/rename command - rename the current session.""" + +from __future__ import annotations + +import inspect +from typing import Any + +from iac_code.commands.registry import CommandResult +from iac_code.i18n import _ +from iac_code.services.session_metadata import normalize_session_name + + +async def rename_command(context=None, args: list[str] | None = None, **_kwargs: Any) -> CommandResult: + """Rename the current interactive session.""" + if context is None or getattr(context, "repl", None) is None: + return CommandResult(_("Rename is only available in interactive mode."), is_error=True) + + repl = context.repl + args = args or [] + if len(args) > 1: + return CommandResult(_("Usage: /rename "), is_error=True) + + if args: + raw_name = args[0] + else: + prompt_for_session_name = getattr(repl, "prompt_for_session_name", None) + if prompt_for_session_name is None: + return CommandResult(_("Rename is only available in interactive mode."), is_error=True) + raw_name = await prompt_for_session_name() + if raw_name is None: + return CommandResult(_("Rename cancelled")) + + try: + name = normalize_session_name(raw_name) + result = repl.rename_current_session(name) + if inspect.isawaitable(result): + result = await result + except ValueError as exc: + return CommandResult(str(exc), is_error=True) + + if result == "unchanged": + return CommandResult(_("Session is already named {name}").format(name=name)) + return CommandResult(_("Renamed session to {name}").format(name=name), refresh_banner=True) diff --git a/src/iac_code/commands/resume.py b/src/iac_code/commands/resume.py index 4d36978..ba5737f 100644 --- a/src/iac_code/commands/resume.py +++ b/src/iac_code/commands/resume.py @@ -5,6 +5,7 @@ from typing import Any from iac_code.i18n import _ +from iac_code.services.session_resolver import ResolutionStatus, resolve_session_argument async def resume_command(context=None, args: list[str] | None = None, **_kwargs: Any) -> str: @@ -27,11 +28,31 @@ async def resume_command(context=None, args: list[str] | None = None, **_kwargs: return _("Resume is unavailable: session index not initialised.") if arg_str: - entry = index.find_by_id_or_prefix(arg_str) - if entry is None: + resolution = resolve_session_argument(index, repl._original_cwd, arg_str) + if resolution.status == ResolutionStatus.NOT_FOUND: return _("Session not found: {arg}").format(arg=arg_str) - await repl.swap_or_announce_session(entry) - return "" + if resolution.status == ResolutionStatus.FOUND: + if resolution.entry is None: + return _("Session not found: {arg}").format(arg=arg_str) + await repl.swap_or_announce_session(resolution.entry) + return "" + if resolution.status == ResolutionStatus.AMBIGUOUS_NAME: + from iac_code.ui.dialogs.resume_picker import ResumePicker + + picker = ResumePicker( + index=index, + current_cwd=repl._original_cwd, + current_session_id=repl.session_id, + keybinding_manager=getattr(repl, "_keybinding_manager", None), + renderer=getattr(repl, "renderer", None), + entries=resolution.candidates, + ) + selected = picker.run() + if selected is None: + return _("Resume cancelled") + await repl.swap_or_announce_session(selected) + return "" + return _("Unable to resolve session: {arg}").format(arg=arg_str) from iac_code.ui.dialogs.resume_picker import ResumePicker 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 201a527..a1e47a8 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 21:26+0800\n" +"POT-Creation-Date: 2026-06-02 22:08+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: de\n" @@ -20,84 +20,133 @@ msgstr "" #: src/iac_code/config.py:164 #, python-brace-format msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}" -msgstr "Ungültiger IAC_CODE_PROVIDER-Wert: {!r}. Gültige Werte (Groß-/Kleinschreibung wird ignoriert): {}" +msgstr "" +"Ungültiger IAC_CODE_PROVIDER-Wert: {!r}. Gültige Werte " +"(Groß-/Kleinschreibung wird ignoriert): {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." +msgid "" +"Unix domain socket transport is not supported on Windows. Use --transport" +" http or --transport stdio instead." msgstr "" -"Unix-Domain-Socket-Transport wird unter Windows nicht unterstützt. Verwenden Sie stattdessen --transport http oder " -"--transport stdio." +"Unix-Domain-Socket-Transport wird unter Windows nicht unterstützt. " +"Verwenden Sie stattdessen --transport http oder --transport stdio." + +#: src/iac_code/acp/server.py:413 src/iac_code/acp/server.py:429 +#: src/iac_code/acp/server.py:456 +msgid "Session not found" +msgstr "Sitzung nicht gefunden" + +#: src/iac_code/acp/server.py:416 +#, python-brace-format +msgid "Session name is ambiguous. Candidates: {candidates}" +msgstr "Der Sitzungsname ist mehrdeutig. Kandidaten: {candidates}" + +#: src/iac_code/acp/server.py:434 src/iac_code/acp/server.py:708 +#, python-brace-format +msgid "Session belongs to another project. Run: {hint}" +msgstr "Die Sitzung gehört zu einem anderen Projekt. Ausführen: {hint}" -#: src/iac_code/acp/slash_registry.py:44 +#: src/iac_code/acp/slash_registry.py:46 #, python-brace-format -msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" -msgstr "Der Befehl '/{cmd_name}' wird über ACP nicht unterstützt. Unterstützte Befehle: {supported}" +msgid "" +"Command '/{cmd_name}' is not supported over ACP. Supported commands: " +"{supported}" +msgstr "" +"Der Befehl '/{cmd_name}' wird über ACP nicht unterstützt. Unterstützte " +"Befehle: {supported}" -#: src/iac_code/acp/slash_registry.py:58 +#: src/iac_code/acp/slash_registry.py:62 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "Der Handler für den Befehl '/{cmd_name}' ist nicht implementiert." -#: src/iac_code/acp/slash_registry.py:70 +#: src/iac_code/acp/slash_registry.py:74 #, python-brace-format msgid "Compaction failed: {error}" msgstr "Komprimierung fehlgeschlagen: {error}" -#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:77 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "Nichts zu komprimieren: Die Konversation ist leer." -#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." -msgstr "Konversation zu kurz zum Komprimieren: Alle Nachrichten liegen im Erhaltungsfenster der letzten {turns} Runden." +msgid "" +"Conversation too short to compact: all messages are within the recent " +"{turns}-turn preservation window." +msgstr "" +"Konversation zu kurz zum Komprimieren: Alle Nachrichten liegen im " +"Erhaltungsfenster der letzten {turns} Runden." -#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:84 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "Komprimierung fehlgeschlagen. Details finden Sie in den Protokollen." -#: src/iac_code/acp/slash_registry.py:85 +#: src/iac_code/acp/slash_registry.py:89 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" -msgstr "Kontext komprimiert: {original} → {compacted} Tokens ({percent} Reduzierung). Kontextnutzung: {usage}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent} reduction)." +" Context usage: {usage}" +msgstr "" +"Kontext komprimiert: {original} → {compacted} Tokens ({percent} " +"Reduzierung). Kontextnutzung: {usage}" -#: src/iac_code/acp/slash_registry.py:99 +#: src/iac_code/acp/slash_registry.py:103 #, python-brace-format msgid "Clear failed: {error}" msgstr "Löschen fehlgeschlagen: {error}" -#: src/iac_code/acp/slash_registry.py:100 +#: src/iac_code/acp/slash_registry.py:104 msgid "Conversation history cleared." msgstr "Konversationsverlauf gelöscht." -#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:120 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "Debug-Protokollierung ist aktiv. Protokolldatei: {path}" -#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "Debug-Protokollierung ist inaktiv." -#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "Debug-Protokollierung aktiviert. Protokolldatei: {path}" -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:129 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "Debug-Protokollierung deaktiviert." -#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:131 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "Verwendung: /debug [on|off]" -#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +#: src/iac_code/acp/slash_registry.py:136 src/iac_code/commands/memory.py:84 msgid "Memory manager is unavailable." msgstr "Der Speicher-Manager ist nicht verfügbar." -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 -#: src/iac_code/ui/repl.py:807 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +msgid "Usage: /rename " +msgstr "Verwendung: /rename " + +#: src/iac_code/acp/slash_registry.py:152 +msgid "Rename is only available after a session is created." +msgstr "Umbenennen ist erst verfügbar, nachdem eine Sitzung erstellt wurde." + +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#, python-brace-format +msgid "Session is already named {name}" +msgstr "Die Sitzung heißt bereits {name}" + +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#, python-brace-format +msgid "Renamed session to {name}" +msgstr "Sitzung in {name} umbenannt" + +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 msgid "Permission denied." msgstr "Zugriff verweigert." @@ -114,7 +163,8 @@ msgstr "Erkunden" msgid "Plan" msgstr "Plan" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 +#: src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "Agent" @@ -166,14 +216,17 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-" +"code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " Lösung: Führen Sie iac-code aus und geben Sie /auth ein\n" -" oder setzen Sie die Umgebungsvariable: IAC_CODE_API_KEY=\n" -" Dokumentation: https://aliyun.github.io/iac-code/de/docs/configuration/authentication\n" +" oder setzen Sie die Umgebungsvariable: IAC_CODE_API_KEY=" +"\n" +" Dokumentation: https://aliyun.github.io/iac-" +"code/de/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -186,7 +239,9 @@ msgstr "Git for Windows wird über npmmirror installiert..." #: src/iac_code/cli/install_git_bash.py:59 msgid "powershell.exe was not found on PATH; cannot run installer." -msgstr "powershell.exe wurde nicht im PATH gefunden; das Installationsprogramm kann nicht ausgeführt werden." +msgstr "" +"powershell.exe wurde nicht im PATH gefunden; das Installationsprogramm " +"kann nicht ausgeführt werden." #: src/iac_code/cli/install_git_bash.py:66 #, python-brace-format @@ -195,11 +250,12 @@ msgstr "Installation fehlgeschlagen (PowerShell beendet mit Code {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " -"non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may " +"have been cancelled or the installer used a non-standard path." msgstr "" -"Installer wurde beendet, aber bash.exe wurde nicht an gängigen Orten gefunden; UAC wurde möglicherweise abgebrochen " -"oder der Installer hat einen nicht standardmäßigen Pfad verwendet." +"Installer wurde beendet, aber bash.exe wurde nicht an gängigen Orten " +"gefunden; UAC wurde möglicherweise abgebrochen oder der Installer hat " +"einen nicht standardmäßigen Pfad verwendet." #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -222,9 +278,14 @@ msgstr "Git for Windows über den npmmirror-Spiegel installieren (nur Windows)." msgid "YAML config file containing A2A client options" msgstr "YAML-Konfigurationsdatei mit A2A-Client-Optionen" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" -msgstr "A2A-Client-Abhängigkeiten fehlen. Installieren mit: pip install 'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 +#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "" +"A2A client dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" +msgstr "" +"A2A-Client-Abhängigkeiten fehlen. Installieren mit: pip install 'iac-" +"code[a2a]'" #: src/iac_code/cli/main.py:82 msgid "LLM model to use" @@ -242,7 +303,8 @@ msgstr "Ausgabeformat: text, json, stream-json" msgid "Maximum agent turns in headless mode" msgstr "Maximale Agent-Runden im Headless-Modus" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 +#: src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "Debug-Protokollierung aktivieren" @@ -255,8 +317,8 @@ msgid "Show version and exit" msgstr "Version anzeigen und beenden" #: src/iac_code/cli/main.py:89 -msgid "Resume a session by ID" -msgstr "Eine Sitzung anhand der ID fortsetzen" +msgid "Resume a session by ID or name" +msgstr "Sitzung per ID oder Name fortsetzen" #: src/iac_code/cli/main.py:90 msgid "Resume the most recent session" @@ -267,12 +329,20 @@ msgid "Install completion for the current shell." msgstr "Vervollständigung für die aktuelle Shell installieren." #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "Show completion for the current shell, to copy it or customize the installation." -msgstr "Vervollständigung für die aktuelle Shell anzeigen, zum Kopieren oder Anpassen der Installation." +msgid "" +"Show completion for the current shell, to copy it or customize the " +"installation." +msgstr "" +"Vervollständigung für die aktuelle Shell anzeigen, zum Kopieren oder " +"Anpassen der Installation." #: src/iac_code/cli/main.py:110 -msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" -msgstr "Durch Komma getrennte Tool-Berechtigungsmuster zum Erlauben, z.B. 'bash(git *),write_file'*),write_file'" +msgid "" +"Comma-separated tool permission patterns to allow, e.g. 'bash(git " +"*),write_file'" +msgstr "" +"Durch Komma getrennte Tool-Berechtigungsmuster zum Erlauben, z.B. " +"'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 msgid "Comma-separated tool permission patterns to deny" @@ -281,14 +351,15 @@ msgstr "Durch Komma getrennte Tool-Berechtigungsmuster zum Verweigern" #: src/iac_code/cli/main.py:120 msgid "Permission mode: default, accept_edits, bypass_permissions, dont_ask" msgstr "" -"Permission mode: default, accept_edits, bypass_permissions, dont_askBerechtigungsmodus: default, accept_edits, " -"bypass_permissions, dont_ask" +"Permission mode: default, accept_edits, bypass_permissions, " +"dont_askBerechtigungsmodus: default, accept_edits, bypass_permissions, " +"dont_ask" #: src/iac_code/cli/main.py:151 msgid "Error: --resume and --continue cannot be used together." msgstr "" -"Error: --resume and --continue cannot be used together.Fehler: --resume und --continue können nicht gemeinsam " -"verwendet werden." +"Error: --resume and --continue cannot be used together.Fehler: --resume " +"und --continue können nicht gemeinsam verwendet werden." #: src/iac_code/cli/main.py:163 #, python-brace-format @@ -320,27 +391,45 @@ msgid "YAML config file for A2A server options" msgstr "YAML-Konfigurationsdatei für A2A-Server-Optionen" #: src/iac_code/cli/main.py:584 -msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." -msgstr "HTTP-Serverport. 41242 ist der von Gemini CLI inspirierte iac-code-Standard, kein registrierter A2A-Port." +msgid "" +"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " +"not a registered A2A port." +msgstr "" +"HTTP-Serverport. 41242 ist der von Gemini CLI inspirierte iac-code-" +"Standard, kein registrierter A2A-Port." #: src/iac_code/cli/main.py:589 -msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" -msgstr "A2A-Transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc oder redis-streams" +msgid "" +"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " +"redis-streams" +msgstr "" +"A2A-Transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc oder " +"redis-streams" #: src/iac_code/cli/main.py:595 -msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." -msgstr "Legt A2A-Thinking-Signaltypen offen; fuer mehrere Werte wiederholen. Werte: raw-thinking, tool-trace." +msgid "" +"Expose A2A thinking signal types; repeat for multiple. Values: raw-" +"thinking, tool-trace." +msgstr "" +"Legt A2A-Thinking-Signaltypen offen; fuer mehrere Werte wiederholen. " +"Werte: raw-thinking, tool-trace." #: src/iac_code/cli/main.py:646 -msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" -msgstr "A2A-Server-Abhängigkeiten fehlen. Installieren mit: pip install 'iac-code[a2a]'" +msgid "" +"A2A server dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" +msgstr "" +"A2A-Server-Abhängigkeiten fehlen. Installieren mit: pip install 'iac-" +"code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "Sendet einen Prompt an einen A2A-JSON-RPC-Endpunkt." -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 +#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 +#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "URL des A2A-JSON-RPC-Endpunkts" @@ -365,33 +454,48 @@ msgstr "Arbeitsverzeichnis-Metadaten, die mit der Anfrage gesendet werden" msgid "A2A context ID to continue" msgstr "Fortzusetzende A2A-Kontext-ID" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 +#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 +#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 +#: src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "Bearer-Token für A2A-HTTP-Anfragen" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 +#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 +#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 +#: src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "Basic-Auth-Benutzername für A2A-HTTP-Anfragen" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 +#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 +#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 +#: src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "Basic-Auth-Passwort für A2A-HTTP-Anfragen" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 +#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 +#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 +#: src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "API-Schlüssel für A2A-HTTP-Anfragen" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 +#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 +#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 +#: src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "HTTP-Header-Name für den A2A-API-Schlüssel" @@ -427,8 +531,10 @@ msgstr "Basis-URL des A2A-Agenten" msgid "Get an A2A task." msgstr "Eine A2A-Aufgabe abrufen." -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 +#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 +#: src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "A2A-Aufgaben-ID" @@ -476,7 +582,8 @@ msgstr "A2A-Aufgaben-Ereignisstrom abonnieren." msgid "Create an A2A task push notification config." msgstr "Eine Push-Benachrichtigungskonfiguration für eine A2A-Aufgabe erstellen." -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 +#: src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "Push-Konfigurations-ID" @@ -540,81 +647,89 @@ msgstr "Verzeichnis für persistierte A2A-Routen" msgid "Save the provided routes as a route snapshot" msgstr "Speichert die angegebenen Routen als Routen-Snapshot" -#: src/iac_code/commands/__init__.py:25 +#: src/iac_code/commands/__init__.py:26 msgid "Show available commands" msgstr "Verfügbare Befehle anzeigen" -#: src/iac_code/commands/__init__.py:34 +#: src/iac_code/commands/__init__.py:35 msgid "Clear conversation history" msgstr "Konversationsverlauf löschen" -#: src/iac_code/commands/__init__.py:42 +#: src/iac_code/commands/__init__.py:43 msgid "Show or switch model" msgstr "Modell anzeigen oder wechseln" -#: src/iac_code/commands/__init__.py:51 +#: src/iac_code/commands/__init__.py:52 msgid "Show or switch thinking effort" msgstr "Thinking-Effort anzeigen oder wechseln" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compact conversation context" msgstr "Konversationskontext komprimieren" -#: src/iac_code/commands/__init__.py:62 +#: src/iac_code/commands/__init__.py:63 msgid "Compacting conversation" msgstr "Konversation wird komprimiert" -#: src/iac_code/commands/__init__.py:69 +#: src/iac_code/commands/__init__.py:70 msgid "Exit the application" msgstr "Anwendung beenden" -#: src/iac_code/commands/__init__.py:78 +#: src/iac_code/commands/__init__.py:79 msgid "Authenticate with LLM provider" msgstr "Beim LLM-Anbieter authentifizieren" -#: src/iac_code/commands/__init__.py:87 +#: src/iac_code/commands/__init__.py:88 msgid "Toggle debug logging" msgstr "Debug-Protokollierung umschalten" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "View and manage persistent memories" msgstr "Persistente Erinnerungen anzeigen und verwalten" -#: src/iac_code/commands/__init__.py:98 +#: src/iac_code/commands/__init__.py:99 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "Resume a previous session" msgstr "Eine frühere Sitzung fortsetzen" -#: src/iac_code/commands/__init__.py:107 +#: src/iac_code/commands/__init__.py:108 msgid "[conversation id or search term]" msgstr "[Konversations-ID oder Suchbegriff]" -#: src/iac_code/commands/__init__.py:114 +#: src/iac_code/commands/__init__.py:115 +msgid "Rename the current session" +msgstr "Aktuelle Sitzung umbenennen" + +#: src/iac_code/commands/__init__.py:124 msgid "Manage skills" msgstr "Skills verwalten" -#: src/iac_code/commands/__init__.py:122 +#: src/iac_code/commands/__init__.py:132 msgid "Show current session status" msgstr "Aktuellen Sitzungsstatus anzeigen" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navigieren" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 -#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Bestätigen" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 -#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 -#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 +#: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 +#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "Zurück" @@ -626,8 +741,9 @@ msgstr "Behalten" msgid "Re-enter" msgstr "Erneut eingeben" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 +#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 +#: src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (aktuell)" @@ -664,17 +780,20 @@ msgstr "IaC-Cloud-Dienst konfigurieren" msgid "Select configuration type" msgstr "Konfigurationstyp auswählen" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 +#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 +#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "Authentifizierung abgebrochen" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 +#: src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "Anbieter auswählen — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 +#: src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "Drittanbieter" @@ -770,8 +889,9 @@ msgstr "Cloud-Anbieter auswählen" msgid "Credential" msgstr "Anmeldedaten" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 -#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 +#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Region" @@ -906,8 +1026,12 @@ msgstr "Keine aktive Agent-Schleife." #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" -msgstr "Kontext komprimiert: {original} → {compacted} Tokens (Reduzierung {percent_display}). Kontextnutzung: {usage_display}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent_display} " +"reduction). Context usage: {usage_display}" +msgstr "" +"Kontext komprimiert: {original} → {compacted} Tokens (Reduzierung " +"{percent_display}). Kontextnutzung: {usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -917,7 +1041,8 @@ msgstr "Der Befehl debug erfordert einen Kontext." msgid "No active session." msgstr "Keine aktive Sitzung." -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 +#: src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "Keine konfigurierten Anbieter. Führen Sie zuerst /auth aus." @@ -1011,8 +1136,12 @@ msgstr "Erinnerung '{name}' gelöscht." #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." -msgstr "Das Modell wird von '{source}' verwaltet. Ändern Sie es in {source} oder wechseln Sie den Provider über /auth." +msgid "" +"Model is managed by '{source}'. To change model, modify it in {source} or" +" switch provider via /auth." +msgstr "" +"Das Modell wird von '{source}' verwaltet. Ändern Sie es in {source} oder " +"wechseln Sie den Provider über /auth." #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1029,23 +1158,36 @@ msgstr "Aktuelles Modell: {model}" msgid "Kept model as {model}" msgstr "Modell beibehalten: {model}" -#: src/iac_code/commands/resume.py:21 +#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +msgid "Rename is only available in interactive mode." +msgstr "Umbenennen ist nur im interaktiven Modus verfügbar." + +#: src/iac_code/commands/rename.py:30 +msgid "Rename cancelled" +msgstr "Umbenennen abgebrochen" + +#: src/iac_code/commands/resume.py:22 msgid "Resume is only available in interactive mode." msgstr "/resume ist nur im interaktiven Modus verfügbar." -#: src/iac_code/commands/resume.py:27 +#: src/iac_code/commands/resume.py:28 msgid "Resume is unavailable: session index not initialised." msgstr "Fortsetzen nicht möglich: Sitzungsindex nicht initialisiert." -#: src/iac_code/commands/resume.py:32 +#: src/iac_code/commands/resume.py:33 src/iac_code/commands/resume.py:36 #, python-brace-format msgid "Session not found: {arg}" msgstr "Sitzung nicht gefunden: {arg}" -#: src/iac_code/commands/resume.py:47 +#: src/iac_code/commands/resume.py:52 src/iac_code/commands/resume.py:68 msgid "Resume cancelled" msgstr "Fortsetzen abgebrochen" +#: src/iac_code/commands/resume.py:55 +#, python-brace-format +msgid "Unable to resolve session: {arg}" +msgstr "Sitzung konnte nicht aufgelöst werden: {arg}" + #: src/iac_code/commands/skills.py:14 msgid "Skills management is only available in interactive mode." msgstr "Skill-Verwaltung ist nur im interaktiven Modus verfügbar." @@ -1070,7 +1212,8 @@ msgstr "Der Befehl status benötigt einen REPL-Kontext." msgid "Status is only available in interactive mode." msgstr "status ist nur im interaktiven Modus verfügbar." -#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:136 +#: src/iac_code/ui/banner.py:138 msgid "Session" msgstr "Sitzung" @@ -1078,7 +1221,8 @@ msgstr "Sitzung" msgid "Provider" msgstr "Anbieter" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "nicht konfiguriert" @@ -1179,39 +1323,48 @@ msgstr "Abgebrochen!" #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." msgstr "" -"Cannot determine provider for model: {model}. Run /auth to configure.Cannot determine provider for model: {model}. " -"Run /auth to configure.Anbieter für Modell {model} kann nicht ermittelt werden. Führen Sie /auth aus, um die " -"Konfiguration vorzunehmen." +"Cannot determine provider for model: {model}. Run /auth to " +"configure.Cannot determine provider for model: {model}. Run /auth to " +"configure.Anbieter für Modell {model} kann nicht ermittelt werden. Führen" +" Sie /auth aus, um die Konfiguration vorzunehmen." #: src/iac_code/providers/manager.py:95 #, python-brace-format msgid "Unknown provider key: '{key}'. Run /auth to configure." -msgstr "Unbekannter Anbieterschlüssel: '{key}'. Führen Sie /auth aus, um die Konfiguration vorzunehmen." +msgstr "" +"Unbekannter Anbieterschlüssel: '{key}'. Führen Sie /auth aus, um die " +"Konfiguration vorzunehmen." #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." +msgid "" +"No API key configured for provider '{provider}' (model: {model}). Run " +"/auth to configure." msgstr "" -"Kein API-Schlüssel für Anbieter '{provider}' konfiguriert (Modell: {model}). Führen Sie /auth aus, um die " -"Konfiguration vorzunehmen." +"Kein API-Schlüssel für Anbieter '{provider}' konfiguriert (Modell: " +"{model}). Führen Sie /auth aus, um die Konfiguration vorzunehmen." #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " -"endpoints require a /v1 suffix (e.g. {base_url}/v1)." +"API returned no data. Please check that your API Base URL is correct " +"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " +"suffix (e.g. {base_url}/v1)." msgstr "" -"Die API hat keine Daten zurückgegeben. Prüfen Sie, ob Ihre API Base URL korrekt ist (aktuell: {base_url}). Viele " -"OpenAI-kompatible Endpunkte erfordern ein /v1-Suffix (z. B. {base_url}/v1)." +"Die API hat keine Daten zurückgegeben. Prüfen Sie, ob Ihre API Base URL " +"korrekt ist (aktuell: {base_url}). Viele OpenAI-kompatible Endpunkte " +"erfordern ein /v1-Suffix (z. B. {base_url}/v1)." #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" -"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." +"API returned an invalid response. Please check that your API Base URL is " +"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" +" /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"Die API hat eine ungültige Antwort zurückgegeben. Prüfen Sie, ob Ihre API Base URL korrekt ist (aktuell: {base_url})." -" Viele OpenAI-kompatible Endpunkte erfordern ein /v1-Suffix (z. B. {base_url}/v1)." +"Die API hat eine ungültige Antwort zurückgegeben. Prüfen Sie, ob Ihre API" +" Base URL korrekt ist (aktuell: {base_url}). Viele OpenAI-kompatible " +"Endpunkte erfordern ein /v1-Suffix (z. B. {base_url}/v1)." #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1292,22 +1445,36 @@ msgstr "Anthropic-kompatibel" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " +"support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " -"settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " +"mode (remove 'llm_source: qwenpaw' from settings.yml)." msgstr "" -"[QwenPaw-Modus] Unbekannter Provider '{provider_id}'. iac-code unterstützt diesen Provider nicht.\n" +"[QwenPaw-Modus] Unbekannter Provider '{provider_id}'. iac-code " +"unterstützt diesen Provider nicht.\n" "Unterstützte QwenPaw-Provider-IDs: {supported_ids}\n" -"Lösung: Wechseln Sie in QwenPaw zu einem unterstützten Provider, oder deaktivieren Sie den QwenPaw-Modus (entfernen " -"Sie 'llm_source: qwenpaw' aus settings.yml)." +"Lösung: Wechseln Sie in QwenPaw zu einem unterstützten Provider, oder " +"deaktivieren Sie den QwenPaw-Modus (entfernen Sie 'llm_source: qwenpaw' " +"aus settings.yml)." + +#: src/iac_code/services/session_metadata.py:52 +#, python-brace-format +msgid "Session name must match {pattern}" +msgstr "Der Sitzungsname muss {pattern} entsprechen" + +#: src/iac_code/services/session_storage.py:241 +#, python-brace-format +msgid "Session name already exists in this project: {name}" +msgstr "Der Sitzungsname ist in diesem Projekt bereits vorhanden: {name}" #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "Ungültiges --permission-mode {!r}. Gültige Werte: {}" -#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 +#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "{} erlauben?" @@ -1375,28 +1542,40 @@ msgid "Waiting for browser authorization" msgstr "Warten auf Browserautorisierung" #: src/iac_code/services/providers/aliyun_oauth.py:300 -msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." -msgstr "1. Der Browser kann official-cli anzeigen; dies ist die offizielle CLI-OAuth-Anwendung von Alibaba Cloud." +msgid "" +"1. The browser may show official-cli; this is the Alibaba Cloud official " +"CLI OAuth application." +msgstr "" +"1. Der Browser kann official-cli anzeigen; dies ist die offizielle CLI-" +"OAuth-Anwendung von Alibaba Cloud." #: src/iac_code/services/providers/aliyun_oauth.py:302 -msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgid "" +"2. If assignment is required, assign the RAM user or RAM role that is " +"signed in. User groups are not supported." msgstr "" -"2. Falls eine Zuweisung erforderlich ist, weisen Sie den angemeldeten RAM-Benutzer oder die RAM-Rolle zu. " -"Benutzergruppen werden nicht unterstützt." +"2. Falls eine Zuweisung erforderlich ist, weisen Sie den angemeldeten " +"RAM-Benutzer oder die RAM-Rolle zu. Benutzergruppen werden nicht " +"unterstützt." #: src/iac_code/services/providers/aliyun_oauth.py:306 msgid "" -"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " -"out of Alibaba Cloud and sign in again." +"3. After assignment, close the old authorization page and run OAuth Login" +" (Browser) again. If it still fails, sign out of Alibaba Cloud and sign " +"in again." msgstr "" -"3. Schließen Sie nach der Zuweisung die alte Autorisierungsseite und führen Sie OAuth-Anmeldung (Browser) erneut aus." -" Falls es weiterhin fehlschlägt, melden Sie sich bei Alibaba Cloud ab und wieder an." +"3. Schließen Sie nach der Zuweisung die alte Autorisierungsseite und " +"führen Sie OAuth-Anmeldung (Browser) erneut aus. Falls es weiterhin " +"fehlschlägt, melden Sie sich bei Alibaba Cloud ab und wieder an." #: src/iac_code/services/providers/aliyun_oauth.py:310 -msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgid "" +"4. STS credentials refresh when possible until Alibaba Cloud expires " +"them. If refresh fails, run /auth again." msgstr "" -"4. STS-Anmeldeinformationen werden nach Möglichkeit aktualisiert, bis Alibaba Cloud sie ablaufen lässt. Wenn die " -"Aktualisierung fehlschlägt, führen Sie /auth erneut aus." +"4. STS-Anmeldeinformationen werden nach Möglichkeit aktualisiert, bis " +"Alibaba Cloud sie ablaufen lässt. Wenn die Aktualisierung fehlschlägt, " +"führen Sie /auth erneut aus." #: src/iac_code/services/providers/aliyun_oauth.py:313 msgid "Press Esc to cancel while waiting." @@ -1404,19 +1583,26 @@ msgstr "Drücken Sie Esc, um das Warten abzubrechen." #: src/iac_code/services/providers/aliyun_oauth.py:321 msgid "" -"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" -" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " -"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to " +"assign the official-cli application, assign it to the exact RAM user or " +"RAM role currently signed in. User groups are not supported. Then close " +"the old authorization page, sign out of Alibaba Cloud and sign in again " +"if needed, and run /auth to choose OAuth Login (Browser) again." msgstr "" -"Zeitüberschreitung beim Warten auf den OAuth-Callback. Wenn Alibaba Cloud Sie aufgefordert hat, die Anwendung " -"official-cli zuzuweisen, weisen Sie sie genau dem aktuell angemeldeten RAM-Benutzer oder der RAM-Rolle zu. " -"Benutzergruppen werden nicht unterstützt. Schließen Sie danach die alte Autorisierungsseite, melden Sie sich bei " -"Alibaba Cloud ab und bei Bedarf wieder an, und führen Sie /auth aus, um erneut OAuth-Anmeldung (Browser) zu wählen." +"Zeitüberschreitung beim Warten auf den OAuth-Callback. Wenn Alibaba Cloud" +" Sie aufgefordert hat, die Anwendung official-cli zuzuweisen, weisen Sie " +"sie genau dem aktuell angemeldeten RAM-Benutzer oder der RAM-Rolle zu. " +"Benutzergruppen werden nicht unterstützt. Schließen Sie danach die alte " +"Autorisierungsseite, melden Sie sich bei Alibaba Cloud ab und bei Bedarf " +"wieder an, und führen Sie /auth aus, um erneut OAuth-Anmeldung (Browser) " +"zu wählen." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." -msgstr "Skill '{name}' ist deaktiviert. Führen Sie /skills aus, um ihn zu aktivieren." +msgstr "" +"Skill '{name}' ist deaktiviert. Führen Sie /skills aus, um ihn zu " +"aktivieren." #: src/iac_code/skills/skill_tool.py:137 #, python-brace-format @@ -1433,8 +1619,12 @@ msgid "Skill disabled: {name}" msgstr "Skill deaktiviert: {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." -msgstr "Geänderten Code auf Wiederverwendbarkeit, Qualität und Effizienz prüfen und gefundene Probleme beheben." +msgid "" +"Review changed code for reuse, quality, and efficiency, then fix issues " +"found." +msgstr "" +"Geänderten Code auf Wiederverwendbarkeit, Qualität und Effizienz prüfen " +"und gefundene Probleme beheben." #: src/iac_code/tools/edit_file.py:116 msgid "Edit" @@ -1532,8 +1722,9 @@ msgstr "Die URL darf nicht leer sein." #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Ungültige URL: Schema fehlt (z. B. http:// oder " -"https://). Erhalten: {url}" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: " +"{url}Ungültige URL: Schema fehlt (z. B. http:// oder https://). Erhalten:" +" {url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1642,7 +1833,8 @@ msgstr "Übereinstimmende Ablehnungsregel(n): {}" msgid "matched ask rule(s): {}" msgstr "Übereinstimmende Abfrageregel(n): {}" -#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 +#: src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "Übereinstimmende Erlaubnisregel(n): {}" @@ -1699,7 +1891,8 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "{action} wird aufgerufen …" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 +#: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Aufruf erfolgreich" @@ -1712,7 +1905,8 @@ msgstr "Antwort empfangen ({count} Zeilen)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 +#: src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "{action} wird ausgeführt …" @@ -1918,8 +2112,9 @@ msgstr "{count} Dokumente gefunden (gesamt {total})" #: src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py:143 msgid "Use web_fetch tool to read full document content if needed." msgstr "" -"Use web_fetch tool to read full document content if needed.Use web_fetch tool to read full document content if " -"needed.Verwenden Sie bei Bedarf das Tool web_fetch für den vollständigen Dokumentinhalt." +"Use web_fetch tool to read full document content if needed.Use web_fetch " +"tool to read full document content if needed.Verwenden Sie bei Bedarf das" +" Tool web_fetch für den vollständigen Dokumentinhalt." #: src/iac_code/tools/cloud/aliyun/ros_stack.py:143 msgid "ROS Stack" @@ -1952,12 +2147,20 @@ msgstr "JSON-Syntaxfehler in der Vorlage (Zeile {line}, Spalte {col}): {msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "Template {fmt} parse result is not an object (dict), please check the template format" -msgstr "Das {fmt}-Parseergebnis der Vorlage ist kein Objekt (dict), bitte überprüfen Sie das Vorlagenformat" +msgid "" +"Template {fmt} parse result is not an object (dict), please check the " +"template format" +msgstr "" +"Das {fmt}-Parseergebnis der Vorlage ist kein Objekt (dict), bitte " +"überprüfen Sie das Vorlagenformat" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" -msgstr "In der Vorlage fehlt ROSTemplateFormatVersion (ROS-Vorlagen müssen dieses Feld enthalten, z.B. '2015-09-01')" +msgid "" +"Template is missing ROSTemplateFormatVersion (ROS templates must include " +"this field, e.g. '2015-09-01')" +msgstr "" +"In der Vorlage fehlt ROSTemplateFormatVersion (ROS-Vorlagen müssen dieses" +" Feld enthalten, z.B. '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" @@ -1970,8 +2173,12 @@ msgstr "Resources muss ein Objekt (dict) sein, aktueller Typ ist {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "Resource '{name}' definition must be an object (dict), current type is {type}" -msgstr "Die Definition der Ressource '{name}' muss ein Objekt (dict) sein, aktueller Typ ist {type}" +msgid "" +"Resource '{name}' definition must be an object (dict), current type is " +"{type}" +msgstr "" +"Die Definition der Ressource '{name}' muss ein Objekt (dict) sein, " +"aktueller Typ ist {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 #, python-brace-format @@ -1984,8 +2191,12 @@ msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" msgstr "Ressource '{name}' hat den falschen Typ '{wrong}', sollte '{correct}' sein" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "Template structure validation found the following issues, please fix and retry:" -msgstr "Die Strukturvalidierung der Vorlage hat folgende Probleme gefunden, bitte beheben und erneut versuchen:" +msgid "" +"Template structure validation found the following issues, please fix and " +"retry:" +msgstr "" +"Die Strukturvalidierung der Vorlage hat folgende Probleme gefunden, bitte" +" beheben und erneut versuchen:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 #, python-brace-format @@ -2005,28 +2216,30 @@ msgstr "Versionshinweise" msgid "Run {} to update." msgstr "Führe {} aus, um zu aktualisieren." -#: src/iac_code/ui/banner.py:105 +#: src/iac_code/ui/banner.py:110 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "Ihr KI-gestützter Infrastructure-as-Code-Assistent" -#: src/iac_code/ui/banner.py:131 +#: src/iac_code/ui/banner.py:144 msgid "Welcome back" msgstr "Willkommen zurück" -#: src/iac_code/ui/banner.py:148 +#: src/iac_code/ui/banner.py:161 msgid "Debug mode" msgstr "Debug-Modus" -#: src/iac_code/ui/banner.py:149 +#: src/iac_code/ui/banner.py:162 msgid "Log file" msgstr "Protokolldatei" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 +#: src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "Nachgedacht für {seconds:.1f}s" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 +#: src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o zum Aufklappen)" @@ -2112,99 +2325,115 @@ msgstr "Nein, immer \"{rule}\" ablehnen (diese Sitzung)" msgid "No, always reject this tool" msgstr "Nein, dieses Tool immer ablehnen" -#: src/iac_code/ui/repl.py:406 +#: src/iac_code/ui/repl.py:412 msgid "Press Ctrl+C again to exit." msgstr "Drücken Sie erneut Ctrl+C zum Beenden." -#: src/iac_code/ui/repl.py:431 +#: src/iac_code/ui/repl.py:437 msgid "Interrupted." msgstr "Unterbrochen." -#: src/iac_code/ui/repl.py:468 -msgid "Goodbye!" -msgstr "Auf Wiedersehen!" - -#: src/iac_code/ui/repl.py:469 -msgid "Resume this session with:" -msgstr "Diese Sitzung fortsetzen mit:" - -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:496 msgid "Update now" msgstr "Jetzt aktualisieren" -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:498 msgid "Run the shown update command and exit when it succeeds." -msgstr "Führt den angezeigten Aktualisierungsbefehl aus und beendet das Programm bei Erfolg." +msgstr "" +"Führt den angezeigten Aktualisierungsbefehl aus und beendet das Programm " +"bei Erfolg." -#: src/iac_code/ui/repl.py:499 +#: src/iac_code/ui/repl.py:501 msgid "Skip" msgstr "Überspringen" -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:503 msgid "Continue with the current version for this session." msgstr "Für diese Sitzung mit der aktuellen Version fortfahren." -#: src/iac_code/ui/repl.py:504 +#: src/iac_code/ui/repl.py:506 msgid "Skip until next version" msgstr "Bis zur nächsten Version überspringen" -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:508 msgid "Hide this update until a newer version is available." msgstr "Dieses Update ausblenden, bis eine neuere Version verfügbar ist." -#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 +#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 msgid "Update command failed. Continuing with the current version." -msgstr "Der Aktualisierungsbefehl ist fehlgeschlagen. Es wird mit der aktuellen Version fortgefahren." +msgstr "" +"Der Aktualisierungsbefehl ist fehlgeschlagen. Es wird mit der aktuellen " +"Version fortgefahren." -#: src/iac_code/ui/repl.py:530 +#: src/iac_code/ui/repl.py:532 msgid "Update completed. Restart iac-code to continue." msgstr "Update abgeschlossen. Starten Sie iac-code neu, um fortzufahren." -#: src/iac_code/ui/repl.py:568 +#: src/iac_code/ui/repl.py:570 msgid "No image in clipboard." msgstr "Kein Bild in der Zwischenablage." -#: src/iac_code/ui/repl.py:754 +#: src/iac_code/ui/repl.py:756 msgid "Usage: !" msgstr "Verwendung: !" -#: src/iac_code/ui/repl.py:759 +#: src/iac_code/ui/repl.py:761 msgid "Shell command support is unavailable." msgstr "Shell-Befehlsunterstützung ist nicht verfügbar." -#: src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "Unbekannter Skill: ${name}. Tippe /, um Befehle und Skills aufzulisten." -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:830 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" -"Unknown command: /{name}. Type /help for available commands.Unbekannter Befehl: /{name}. Geben Sie /help für " -"verfügbare Befehle ein." +"Unknown command: /{name}. Type /help for available commands.Unbekannter " +"Befehl: /{name}. Geben Sie /help für verfügbare Befehle ein." -#: src/iac_code/ui/repl.py:833 +#: src/iac_code/ui/repl.py:835 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ ruft nur Skills auf. Verwende stattdessen /{name}." -#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 +#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 #, python-brace-format msgid "Command error: {error}" msgstr "Befehlsfehler: {error}" -#: src/iac_code/ui/repl.py:862 +#: src/iac_code/ui/repl.py:864 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Kein Handler für Befehl: {name}" -#: src/iac_code/ui/repl.py:1167 +#: src/iac_code/ui/repl.py:1126 +msgid "Goodbye!" +msgstr "Auf Wiedersehen!" + +#: src/iac_code/ui/repl.py:1127 +msgid "Resume this session with:" +msgstr "Diese Sitzung fortsetzen mit:" + +#: src/iac_code/ui/repl.py:1130 +msgid "Session ID" +msgstr "Sitzungs-ID" + +#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sitzung nicht gefunden: {session_id}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1233 +msgid "Session name: " +msgstr "Sitzungsname: " + +#: src/iac_code/ui/repl.py:1239 +msgid "Session name cannot be empty." +msgstr "Der Sitzungsname darf nicht leer sein." + +#: src/iac_code/ui/repl.py:1251 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2215,33 +2444,43 @@ msgstr "" "Zum Fortsetzen ausführen:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1287 +#: src/iac_code/ui/repl.py:1257 +msgid "Multiple sessions match. Resume one by ID:" +msgstr "Mehrere Sitzungen passen. Setzen Sie eine per ID fort:" + +#: src/iac_code/ui/repl.py:1370 msgid "This conversation is from a different directory." msgstr "Diese Konversation stammt aus einem anderen Verzeichnis." -#: src/iac_code/ui/repl.py:1289 +#: src/iac_code/ui/repl.py:1372 msgid "To resume, run:" msgstr "Zum Fortsetzen ausführen:" -#: src/iac_code/ui/repl.py:1294 +#: src/iac_code/ui/repl.py:1377 msgid "(Command copied to clipboard)" msgstr "(Befehl in die Zwischenablage kopiert)" -#: src/iac_code/ui/repl.py:1451 +#: src/iac_code/ui/repl.py:1534 #, python-brace-format -msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." +msgid "" +"Current model {model} does not support image input. Use /model to switch " +"to a vision-capable model." msgstr "" -"Das aktuelle Modell {model} unterstützt keine Bildeingabe. Verwenden Sie /model, um zu einem Vision-fähigen Modell zu" -" wechseln." +"Das aktuelle Modell {model} unterstützt keine Bildeingabe. Verwenden Sie " +"/model, um zu einem Vision-fähigen Modell zu wechseln." -#: src/iac_code/ui/repl.py:1460 +#: src/iac_code/ui/repl.py:1543 #, python-brace-format msgid "Image error: {err}" msgstr "Bildfehler: {err}" -#: src/iac_code/ui/repl.py:1477 -msgid "Failed to persist image to cache; it will only exist in memory for this turn." -msgstr "Bild konnte nicht im Cache gespeichert werden; es existiert nur im Arbeitsspeicher für diesen Durchgang." +#: src/iac_code/ui/repl.py:1560 +msgid "" +"Failed to persist image to cache; it will only exist in memory for this " +"turn." +msgstr "" +"Bild konnte nicht im Cache gespeichert werden; es existiert nur im " +"Arbeitsspeicher für diesen Durchgang." #: src/iac_code/ui/spinner.py:52 msgid "Processing" @@ -2331,87 +2570,87 @@ msgstr "Tippen, um Dateien zu suchen …" msgid "No matching files" msgstr "Keine passenden Dateien" -#: src/iac_code/ui/dialogs/resume_picker.py:114 +#: src/iac_code/ui/dialogs/resume_picker.py:116 msgid "Search..." msgstr "Suchen …" -#: src/iac_code/ui/dialogs/resume_picker.py:359 +#: src/iac_code/ui/dialogs/resume_picker.py:374 msgid "Resume Session" msgstr "Sitzung fortsetzen" -#: src/iac_code/ui/dialogs/resume_picker.py:369 +#: src/iac_code/ui/dialogs/resume_picker.py:384 msgid "No sessions found" msgstr "Keine Sitzungen gefunden" -#: src/iac_code/ui/dialogs/resume_picker.py:427 +#: src/iac_code/ui/dialogs/resume_picker.py:444 msgid "show current dir" msgstr "aktuelles Verzeichnis anzeigen" -#: src/iac_code/ui/dialogs/resume_picker.py:429 +#: src/iac_code/ui/dialogs/resume_picker.py:446 msgid "show all projects" msgstr "alle Projekte anzeigen" -#: src/iac_code/ui/dialogs/resume_picker.py:432 +#: src/iac_code/ui/dialogs/resume_picker.py:449 msgid "show all branches" msgstr "alle Branches anzeigen" -#: src/iac_code/ui/dialogs/resume_picker.py:434 +#: src/iac_code/ui/dialogs/resume_picker.py:451 msgid "only show current branch" msgstr "nur aktuellen Branch anzeigen" -#: src/iac_code/ui/dialogs/resume_picker.py:435 +#: src/iac_code/ui/dialogs/resume_picker.py:452 msgid "preview" msgstr "Vorschau" -#: src/iac_code/ui/dialogs/resume_picker.py:436 +#: src/iac_code/ui/dialogs/resume_picker.py:453 msgid "Type to search" msgstr "Tippen, um zu suchen" -#: src/iac_code/ui/dialogs/resume_picker.py:437 +#: src/iac_code/ui/dialogs/resume_picker.py:454 msgid "cancel" msgstr "abbrechen" -#: src/iac_code/ui/dialogs/resume_picker.py:552 +#: src/iac_code/ui/dialogs/resume_picker.py:569 #, python-brace-format msgid "{n} more line{s}" msgstr "{n} weitere Zeile{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:566 +#: src/iac_code/ui/dialogs/resume_picker.py:583 #, python-brace-format msgid "{n} message{s}" msgstr "{n} Nachricht{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:580 +#: src/iac_code/ui/dialogs/resume_picker.py:597 msgid "resume" msgstr "fortsetzen" -#: src/iac_code/ui/dialogs/resume_picker.py:584 +#: src/iac_code/ui/dialogs/resume_picker.py:601 msgid "back" msgstr "zurück" -#: src/iac_code/ui/dialogs/resume_picker.py:589 +#: src/iac_code/ui/dialogs/resume_picker.py:606 msgid "scroll" msgstr "scrollen" -#: src/iac_code/ui/dialogs/resume_picker.py:608 +#: src/iac_code/ui/dialogs/resume_picker.py:625 msgid "(empty session)" msgstr "(leere Sitzung)" -#: src/iac_code/ui/dialogs/resume_picker.py:728 +#: src/iac_code/ui/dialogs/resume_picker.py:745 msgid "just now" msgstr "gerade eben" -#: src/iac_code/ui/dialogs/resume_picker.py:731 +#: src/iac_code/ui/dialogs/resume_picker.py:748 #, python-brace-format msgid "{n} minute{s} ago" msgstr "vor {n} Minute{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:734 +#: src/iac_code/ui/dialogs/resume_picker.py:751 #, python-brace-format msgid "{n} hour{s} ago" msgstr "vor {n} Stunde{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:736 +#: src/iac_code/ui/dialogs/resume_picker.py:753 #, python-brace-format msgid "{n} day{s} ago" msgstr "vor {n} Tag{s}" @@ -2431,8 +2670,12 @@ msgstr "{current} von {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" -msgstr "{count} Skills - Leertaste zum Umschalten, Enter zum Speichern, Tab zum Sortieren, Esc zum Abbrechen" +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "" +"{count} Skills - Leertaste zum Umschalten, Enter zum Speichern, Tab zum " +"Sortieren, Esc zum Abbrechen" #: src/iac_code/ui/dialogs/skills_picker.py:171 #, python-brace-format @@ -2518,8 +2761,12 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code unter Windows erfordert Git for Windows." #: src/iac_code/utils/platform.py:41 -msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." -msgstr "Falls installiert, aber nicht im PATH, setzen Sie die Umgebungsvariable IAC_CODE_GIT_BASH_PATH." +msgid "" +"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " +"variable." +msgstr "" +"Falls installiert, aber nicht im PATH, setzen Sie die Umgebungsvariable " +"IAC_CODE_GIT_BASH_PATH." #: src/iac_code/utils/platform.py:44 msgid "To install:" @@ -2530,8 +2777,12 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " Option 1 - winget (erfordert Zugang zu github.com):" #: src/iac_code/utils/platform.py:52 -msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" -msgstr " Option 2 - falls Sie github.com nicht erreichen können, führen Sie dies aus, um über npmmirror zu installieren:" +msgid "" +" Option 2 - if you cannot reach github.com, run this to install via " +"npmmirror:" +msgstr "" +" Option 2 - falls Sie github.com nicht erreichen können, führen Sie dies" +" aus, um über npmmirror zu installieren:" #~ msgid "toggle preview" #~ msgstr "Vorschau umschalten" @@ -2554,8 +2805,14 @@ msgstr " Option 2 - falls Sie github.com nicht erreichen können, führen Sie d #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." -#~ msgstr "LLM-Anbieter ist durch '{source}' gesperrt. Zum Ändern passen Sie llm_source in settings.yml an." +#~ msgid "" +#~ "LLM provider is locked by '{source}'." +#~ " To change, modify llm_source in " +#~ "settings.yml." +#~ msgstr "" +#~ "LLM-Anbieter ist durch '{source}' " +#~ "gesperrt. Zum Ändern passen Sie " +#~ "llm_source in settings.yml an." #~ msgid "Provider switched: {status}" #~ msgstr "Provider gewechselt: {status}" @@ -2572,6 +2829,16 @@ msgstr " Option 2 - falls Sie github.com nicht erreichen können, führen Sie d #~ msgid "Cache create" #~ msgstr "Cache-Erstellung" -#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" -#~ msgstr "{count} Skills - Leertaste zum Umschalten, Enter zum Speichern, / zum Suchen, t zum Sortieren, Esc zum Abbrechen" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "" +#~ "{count} Skills - Leertaste zum " +#~ "Umschalten, Enter zum Speichern, / zum" +#~ " Suchen, t zum Sortieren, Esc zum " +#~ "Abbrechen" + +#~ msgid "Resume a session by ID" +#~ msgstr "Eine Sitzung anhand der ID fortsetzen" 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 f4f4928..c26fb1f 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 21:26+0800\n" +"POT-Creation-Date: 2026-06-02 22:08+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: es\n" @@ -20,88 +20,136 @@ msgstr "" #: src/iac_code/config.py:164 #, python-brace-format msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}" -msgstr "Valor de IAC_CODE_PROVIDER no válido: {!r}. Valores válidos (sin distinguir mayúsculas/minúsculas): {}" +msgstr "" +"Valor de IAC_CODE_PROVIDER no válido: {!r}. Valores válidos (sin " +"distinguir mayúsculas/minúsculas): {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." +msgid "" +"Unix domain socket transport is not supported on Windows. Use --transport" +" http or --transport stdio instead." msgstr "" -"El transporte de socket de dominio Unix no es compatible con Windows. Use --transport http o --transport stdio en su " -"lugar." +"El transporte de socket de dominio Unix no es compatible con Windows. Use" +" --transport http o --transport stdio en su lugar." -#: src/iac_code/acp/slash_registry.py:44 +#: src/iac_code/acp/server.py:413 src/iac_code/acp/server.py:429 +#: src/iac_code/acp/server.py:456 +msgid "Session not found" +msgstr "Sesión no encontrada" + +#: src/iac_code/acp/server.py:416 #, python-brace-format -msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" -msgstr "El comando '/{cmd_name}' no está admitido en ACP. Comandos admitidos: {supported}" +msgid "Session name is ambiguous. Candidates: {candidates}" +msgstr "El nombre de sesión es ambiguo. Sesiones candidatas: {candidates}" -#: src/iac_code/acp/slash_registry.py:58 +#: src/iac_code/acp/server.py:434 src/iac_code/acp/server.py:708 +#, python-brace-format +msgid "Session belongs to another project. Run: {hint}" +msgstr "La sesión pertenece a otro proyecto. Ejecuta: {hint}" + +#: src/iac_code/acp/slash_registry.py:46 +#, python-brace-format +msgid "" +"Command '/{cmd_name}' is not supported over ACP. Supported commands: " +"{supported}" +msgstr "" +"El comando '/{cmd_name}' no está admitido en ACP. Comandos admitidos: " +"{supported}" + +#: src/iac_code/acp/slash_registry.py:62 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "El controlador del comando '/{cmd_name}' no está implementado." -#: src/iac_code/acp/slash_registry.py:70 +#: src/iac_code/acp/slash_registry.py:74 #, python-brace-format msgid "Compaction failed: {error}" msgstr "Error al compactar: {error}" -#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:77 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "Nada que compactar: la conversación está vacía." -#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." +msgid "" +"Conversation too short to compact: all messages are within the recent " +"{turns}-turn preservation window." msgstr "" -"Conversación demasiado corta para compactar: todos los mensajes están dentro de la ventana de retención de las " -"últimas {turns} interacciones." +"Conversación demasiado corta para compactar: todos los mensajes están " +"dentro de la ventana de retención de las últimas {turns} interacciones." -#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:84 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "" -"Compaction failed. See logs for details.Compaction failed. See logs for details.La compactación falló. Consulte los " -"registros para obtener más información." +"Compaction failed. See logs for details.Compaction failed. See logs for " +"details.La compactación falló. Consulte los registros para obtener más " +"información." -#: src/iac_code/acp/slash_registry.py:85 +#: src/iac_code/acp/slash_registry.py:89 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" -msgstr "Contexto compactado: {original} → {compacted} tokens (reducción del {percent}). Uso del contexto: {usage}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent} reduction)." +" Context usage: {usage}" +msgstr "" +"Contexto compactado: {original} → {compacted} tokens (reducción del " +"{percent}). Uso del contexto: {usage}" -#: src/iac_code/acp/slash_registry.py:99 +#: src/iac_code/acp/slash_registry.py:103 #, python-brace-format msgid "Clear failed: {error}" msgstr "Error al borrar: {error}" -#: src/iac_code/acp/slash_registry.py:100 +#: src/iac_code/acp/slash_registry.py:104 msgid "Conversation history cleared." msgstr "Historial de conversación borrado." -#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:120 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "El registro de depuración está activado. Archivo de registro: {path}" -#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "El registro de depuración está desactivado." -#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "Registro de depuración habilitado. Archivo de registro: {path}" -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:129 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "Registro de depuración deshabilitado." -#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:131 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "Uso: /debug [on|off]" -#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +#: src/iac_code/acp/slash_registry.py:136 src/iac_code/commands/memory.py:84 msgid "Memory manager is unavailable." msgstr "El gestor de memoria no está disponible." -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 -#: src/iac_code/ui/repl.py:807 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +msgid "Usage: /rename " +msgstr "Uso: /rename " + +#: src/iac_code/acp/slash_registry.py:152 +msgid "Rename is only available after a session is created." +msgstr "El cambio de nombre solo está disponible después de crear una sesión." + +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#, python-brace-format +msgid "Session is already named {name}" +msgstr "La sesión ya se llama {name}" + +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#, python-brace-format +msgid "Renamed session to {name}" +msgstr "Sesión renombrada a {name}" + +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 msgid "Permission denied." msgstr "Permiso denegado." @@ -109,8 +157,8 @@ msgstr "Permiso denegado." #, python-brace-format msgid "Done ({tool_count} tool uses · {token_display} tokens)" msgstr "" -"Done ({tool_count} tool uses · {token_display} tokens)Completado ({tool_count} usos de herramientas · {token_display}" -" tokens)" +"Done ({tool_count} tool uses · {token_display} tokens)Completado " +"({tool_count} usos de herramientas · {token_display} tokens)" #: src/iac_code/agent/agent_tool.py:276 msgid "Explore" @@ -120,7 +168,8 @@ msgstr "Explorar" msgid "Plan" msgstr "Plan" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 +#: src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "Agente" @@ -172,14 +221,16 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-" +"code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " Solución: ejecute iac-code y escriba /auth\n" " o configure la variable de entorno: IAC_CODE_API_KEY=\n" -" Documentación: https://aliyun.github.io/iac-code/es/docs/configuration/authentication\n" +" Documentación: https://aliyun.github.io/iac-" +"code/es/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -201,11 +252,12 @@ msgstr "La instalación falló (PowerShell salió con código {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " -"non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may " +"have been cancelled or the installer used a non-standard path." msgstr "" -"El instalador terminó pero bash.exe no se encontró en las ubicaciones habituales; UAC pudo haber sido cancelado o el " -"instalador usó una ruta no estándar." +"El instalador terminó pero bash.exe no se encontró en las ubicaciones " +"habituales; UAC pudo haber sido cancelado o el instalador usó una ruta no" +" estándar." #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -228,9 +280,14 @@ msgstr "Instalar Git for Windows mediante el espejo npmmirror (solo Windows)." msgid "YAML config file containing A2A client options" msgstr "Archivo de configuración YAML con opciones del cliente A2A" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" -msgstr "Faltan las dependencias del cliente A2A. Instálalas con: pip install 'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 +#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "" +"A2A client dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" +msgstr "" +"Faltan las dependencias del cliente A2A. Instálalas con: pip install " +"'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 msgid "LLM model to use" @@ -248,7 +305,8 @@ msgstr "Formato de salida: text, json, stream-json" msgid "Maximum agent turns in headless mode" msgstr "Turnos máximos del agente en modo sin interfaz" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 +#: src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "Habilitar el registro de depuración" @@ -261,8 +319,8 @@ msgid "Show version and exit" msgstr "Mostrar la versión y salir" #: src/iac_code/cli/main.py:89 -msgid "Resume a session by ID" -msgstr "Reanudar una sesión por ID" +msgid "Resume a session by ID or name" +msgstr "Reanudar una sesión por ID o nombre" #: src/iac_code/cli/main.py:90 msgid "Resume the most recent session" @@ -273,12 +331,20 @@ msgid "Install completion for the current shell." msgstr "Instalar la finalización automática para el shell actual." #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "Show completion for the current shell, to copy it or customize the installation." -msgstr "Mostrar el script de finalización del shell actual para copiarlo o personalizar la instalación." +msgid "" +"Show completion for the current shell, to copy it or customize the " +"installation." +msgstr "" +"Mostrar el script de finalización del shell actual para copiarlo o " +"personalizar la instalación." #: src/iac_code/cli/main.py:110 -msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" -msgstr "Patrones de permisos de herramientas a permitir (separados por comas), p.ej. 'bash(git *),write_file'*),write_file'" +msgid "" +"Comma-separated tool permission patterns to allow, e.g. 'bash(git " +"*),write_file'" +msgstr "" +"Patrones de permisos de herramientas a permitir (separados por comas), " +"p.ej. 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 msgid "Comma-separated tool permission patterns to deny" @@ -322,29 +388,45 @@ msgid "YAML config file for A2A server options" msgstr "Archivo de configuración YAML para opciones del servidor A2A" #: src/iac_code/cli/main.py:584 -msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." +msgid "" +"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " +"not a registered A2A port." msgstr "" -"Puerto del servidor HTTP. 41242 es el valor predeterminado de iac-code inspirado en Gemini CLI, no un puerto A2A " -"registrado." +"Puerto del servidor HTTP. 41242 es el valor predeterminado de iac-code " +"inspirado en Gemini CLI, no un puerto A2A registrado." #: src/iac_code/cli/main.py:589 -msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" -msgstr "Transporte A2A: http, stdio, unix, websocket, grpc, grpc-jsonrpc o redis-streams" +msgid "" +"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " +"redis-streams" +msgstr "" +"Transporte A2A: http, stdio, unix, websocket, grpc, grpc-jsonrpc o redis-" +"streams" #: src/iac_code/cli/main.py:595 -msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." -msgstr "Expone tipos de señal de thinking A2A; repite para varios. Valores: raw-thinking, tool-trace." +msgid "" +"Expose A2A thinking signal types; repeat for multiple. Values: raw-" +"thinking, tool-trace." +msgstr "" +"Expone tipos de señal de thinking A2A; repite para varios. Valores: raw-" +"thinking, tool-trace." #: src/iac_code/cli/main.py:646 -msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" -msgstr "Faltan las dependencias del servidor A2A. Instálalas con: pip install 'iac-code[a2a]'" +msgid "" +"A2A server dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" +msgstr "" +"Faltan las dependencias del servidor A2A. Instálalas con: pip install " +"'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "Envía un prompt a un endpoint JSON-RPC A2A." -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 +#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 +#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "URL del endpoint JSON-RPC A2A" @@ -369,33 +451,48 @@ msgstr "Metadatos del directorio de trabajo a enviar con la solicitud" msgid "A2A context ID to continue" msgstr "ID de contexto A2A a continuar" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 +#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 +#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 +#: src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "Token Bearer para solicitudes HTTP A2A" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 +#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 +#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 +#: src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "Nombre de usuario de autenticación básica para solicitudes HTTP A2A" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 +#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 +#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 +#: src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "Contraseña de autenticación básica para solicitudes HTTP A2A" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 +#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 +#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 +#: src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "Clave de API para solicitudes HTTP A2A" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 +#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 +#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 +#: src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "Nombre del encabezado HTTP para la clave de API A2A" @@ -431,8 +528,10 @@ msgstr "URL base del agente A2A" msgid "Get an A2A task." msgstr "Obtén una tarea A2A." -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 +#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 +#: src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "ID de tarea A2A" @@ -480,7 +579,8 @@ msgstr "Suscríbete a un flujo de eventos de tarea A2A." msgid "Create an A2A task push notification config." msgstr "Crea una configuración de notificación push de tarea A2A." -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 +#: src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "ID de configuración push" @@ -534,7 +634,9 @@ msgstr "ID de habilidad a resolver" #: src/iac_code/cli/main.py:1461 msgid "Prompt text used for tag/name route matching" -msgstr "Texto del prompt utilizado para la coincidencia de rutas por etiqueta/nombre" +msgstr "" +"Texto del prompt utilizado para la coincidencia de rutas por " +"etiqueta/nombre" #: src/iac_code/cli/main.py:1466 msgid "Directory for persisted A2A routes" @@ -544,81 +646,89 @@ msgstr "Directorio para rutas A2A persistentes" msgid "Save the provided routes as a route snapshot" msgstr "Guarda las rutas proporcionadas como una instantánea de rutas" -#: src/iac_code/commands/__init__.py:25 +#: src/iac_code/commands/__init__.py:26 msgid "Show available commands" msgstr "Mostrar los comandos disponibles" -#: src/iac_code/commands/__init__.py:34 +#: src/iac_code/commands/__init__.py:35 msgid "Clear conversation history" msgstr "Borrar el historial de conversación" -#: src/iac_code/commands/__init__.py:42 +#: src/iac_code/commands/__init__.py:43 msgid "Show or switch model" msgstr "Mostrar o cambiar de modelo" -#: src/iac_code/commands/__init__.py:51 +#: src/iac_code/commands/__init__.py:52 msgid "Show or switch thinking effort" msgstr "Mostrar o cambiar el nivel de razonamiento (effort)" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compact conversation context" msgstr "Compactar el contexto de la conversación" -#: src/iac_code/commands/__init__.py:62 +#: src/iac_code/commands/__init__.py:63 msgid "Compacting conversation" msgstr "Compactando la conversación" -#: src/iac_code/commands/__init__.py:69 +#: src/iac_code/commands/__init__.py:70 msgid "Exit the application" msgstr "Salir de la aplicación" -#: src/iac_code/commands/__init__.py:78 +#: src/iac_code/commands/__init__.py:79 msgid "Authenticate with LLM provider" msgstr "Autenticar con el proveedor LLM" -#: src/iac_code/commands/__init__.py:87 +#: src/iac_code/commands/__init__.py:88 msgid "Toggle debug logging" msgstr "Activar o desactivar el registro de depuración" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "View and manage persistent memories" msgstr "Ver y administrar memorias persistentes" -#: src/iac_code/commands/__init__.py:98 +#: src/iac_code/commands/__init__.py:99 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "Resume a previous session" msgstr "Reanudar una sesión anterior" -#: src/iac_code/commands/__init__.py:107 +#: src/iac_code/commands/__init__.py:108 msgid "[conversation id or search term]" msgstr "[id de conversación o término de búsqueda]" -#: src/iac_code/commands/__init__.py:114 +#: src/iac_code/commands/__init__.py:115 +msgid "Rename the current session" +msgstr "Renombrar la sesión actual" + +#: src/iac_code/commands/__init__.py:124 msgid "Manage skills" msgstr "Gestionar habilidades" -#: src/iac_code/commands/__init__.py:122 +#: src/iac_code/commands/__init__.py:132 msgid "Show current session status" msgstr "Mostrar el estado actual de la sesión" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navegar" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 -#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmar" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 -#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 -#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 +#: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 +#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "Atrás" @@ -630,8 +740,9 @@ msgstr "Conservar" msgid "Re-enter" msgstr "Volver a introducir" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 +#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 +#: src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (actual)" @@ -668,17 +779,20 @@ msgstr "Configurar servicio cloud IaC" msgid "Select configuration type" msgstr "Seleccionar tipo de configuración" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 +#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 +#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "Autenticación cancelada" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 +#: src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "Seleccionar proveedor — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 +#: src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "Terceros" @@ -774,8 +888,9 @@ msgstr "Seleccionar proveedor de cloud" msgid "Credential" msgstr "Credencial" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 -#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 +#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Región" @@ -910,8 +1025,12 @@ msgstr "No hay ningún bucle de agente activo." #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" -msgstr "Contexto compactado: {original} → {compacted} tokens (reducción {percent_display}). Uso del contexto: {usage_display}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent_display} " +"reduction). Context usage: {usage_display}" +msgstr "" +"Contexto compactado: {original} → {compacted} tokens (reducción " +"{percent_display}). Uso del contexto: {usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -921,7 +1040,8 @@ msgstr "El comando debug requiere un contexto." msgid "No active session." msgstr "No hay ninguna sesión activa." -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 +#: src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "No hay proveedores configurados. Ejecute /auth primero." @@ -1015,8 +1135,12 @@ msgstr "Memoria '{name}' eliminada." #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." -msgstr "El modelo es gestionado por '{source}'. Para cambiarlo, modifíquelo en {source} o cambie de proveedor mediante /auth." +msgid "" +"Model is managed by '{source}'. To change model, modify it in {source} or" +" switch provider via /auth." +msgstr "" +"El modelo es gestionado por '{source}'. Para cambiarlo, modifíquelo en " +"{source} o cambie de proveedor mediante /auth." #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1033,25 +1157,38 @@ msgstr "Modelo actual: {model}" msgid "Kept model as {model}" msgstr "Se mantiene el modelo como {model}" -#: src/iac_code/commands/resume.py:21 +#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +msgid "Rename is only available in interactive mode." +msgstr "El cambio de nombre solo está disponible en modo interactivo." + +#: src/iac_code/commands/rename.py:30 +msgid "Rename cancelled" +msgstr "Cambio de nombre cancelado" + +#: src/iac_code/commands/resume.py:22 msgid "Resume is only available in interactive mode." msgstr "Reanudar solo está disponible en modo interactivo." -#: src/iac_code/commands/resume.py:27 +#: src/iac_code/commands/resume.py:28 msgid "Resume is unavailable: session index not initialised." msgstr "" -"Resume is unavailable: session index not initialised.Reanudar no está disponible: el índice de sesiones no se ha " -"inicializado." +"Resume is unavailable: session index not initialised.Reanudar no está " +"disponible: el índice de sesiones no se ha inicializado." -#: src/iac_code/commands/resume.py:32 +#: src/iac_code/commands/resume.py:33 src/iac_code/commands/resume.py:36 #, python-brace-format msgid "Session not found: {arg}" msgstr "Sesión no encontrada: {arg}" -#: src/iac_code/commands/resume.py:47 +#: src/iac_code/commands/resume.py:52 src/iac_code/commands/resume.py:68 msgid "Resume cancelled" msgstr "Reanudación cancelada" +#: src/iac_code/commands/resume.py:55 +#, python-brace-format +msgid "Unable to resolve session: {arg}" +msgstr "No se pudo resolver la sesión: {arg}" + #: src/iac_code/commands/skills.py:14 msgid "Skills management is only available in interactive mode." msgstr "La gestión de habilidades solo está disponible en modo interactivo." @@ -1076,7 +1213,8 @@ msgstr "El comando status requiere un contexto REPL." msgid "Status is only available in interactive mode." msgstr "status solo está disponible en modo interactivo." -#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:136 +#: src/iac_code/ui/banner.py:138 msgid "Session" msgstr "Sesión" @@ -1084,7 +1222,8 @@ msgstr "Sesión" msgid "Provider" msgstr "Proveedor" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "no configurado" @@ -1185,8 +1324,10 @@ msgstr "¡Operación cancelada!" #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." msgstr "" -"Cannot determine provider for model: {model}. Run /auth to configure.Cannot determine provider for model: {model}. " -"Run /auth to configure.No se puede determinar el proveedor para el modelo: {model}. Ejecute /auth para configurar." +"Cannot determine provider for model: {model}. Run /auth to " +"configure.Cannot determine provider for model: {model}. Run /auth to " +"configure.No se puede determinar el proveedor para el modelo: {model}. " +"Ejecute /auth para configurar." #: src/iac_code/providers/manager.py:95 #, python-brace-format @@ -1195,26 +1336,34 @@ msgstr "Clave de proveedor desconocida: '{key}'. Ejecute /auth para configurar." #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." -msgstr "No se ha configurado una clave API para el proveedor '{provider}' (modelo: {model}). Ejecute /auth para configurar." +msgid "" +"No API key configured for provider '{provider}' (model: {model}). Run " +"/auth to configure." +msgstr "" +"No se ha configurado una clave API para el proveedor '{provider}' " +"(modelo: {model}). Ejecute /auth para configurar." #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " -"endpoints require a /v1 suffix (e.g. {base_url}/v1)." +"API returned no data. Please check that your API Base URL is correct " +"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " +"suffix (e.g. {base_url}/v1)." msgstr "" -"La API no devolvió datos. Compruebe que la API Base URL es correcta (actual: {base_url}). Muchos endpoints " -"compatibles con OpenAI requieren el sufijo /v1 (p. ej., {base_url}/v1)." +"La API no devolvió datos. Compruebe que la API Base URL es correcta " +"(actual: {base_url}). Muchos endpoints compatibles con OpenAI requieren " +"el sufijo /v1 (p. ej., {base_url}/v1)." #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" -"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." +"API returned an invalid response. Please check that your API Base URL is " +"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" +" /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"La API devolvió una respuesta no válida. Compruebe que la API Base URL es correcta (actual: {base_url}). Muchos " -"endpoints compatibles con OpenAI requieren el sufijo /v1 (p. ej., {base_url}/v1)." +"La API devolvió una respuesta no válida. Compruebe que la API Base URL es" +" correcta (actual: {base_url}). Muchos endpoints compatibles con OpenAI " +"requieren el sufijo /v1 (p. ej., {base_url}/v1)." #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1295,22 +1444,35 @@ msgstr "Compatible con Anthropic" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " +"support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " -"settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " +"mode (remove 'llm_source: qwenpaw' from settings.yml)." msgstr "" -"[Modo QwenPaw] Proveedor desconocido '{provider_id}'. iac-code no soporta este proveedor.\n" +"[Modo QwenPaw] Proveedor desconocido '{provider_id}'. iac-code no soporta" +" este proveedor.\n" "IDs de proveedores QwenPaw soportados: {supported_ids}\n" -"Solución: cambie a un proveedor soportado en QwenPaw, o desactive el modo QwenPaw (elimine 'llm_source: qwenpaw' de " -"settings.yml)." +"Solución: cambie a un proveedor soportado en QwenPaw, o desactive el modo" +" QwenPaw (elimine 'llm_source: qwenpaw' de settings.yml)." + +#: src/iac_code/services/session_metadata.py:52 +#, python-brace-format +msgid "Session name must match {pattern}" +msgstr "El nombre de sesión debe coincidir con {pattern}" + +#: src/iac_code/services/session_storage.py:241 +#, python-brace-format +msgid "Session name already exists in this project: {name}" +msgstr "El nombre de sesión ya existe en este proyecto: {name}" #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "Modo --permission-mode no válido: {!r}. Valores válidos: {}" -#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 +#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "¿Permitir {}?" @@ -1378,28 +1540,38 @@ msgid "Waiting for browser authorization" msgstr "Esperando la autorización del navegador" #: src/iac_code/services/providers/aliyun_oauth.py:300 -msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." -msgstr "1. El navegador puede mostrar official-cli; es la aplicación OAuth oficial de la CLI de Alibaba Cloud." +msgid "" +"1. The browser may show official-cli; this is the Alibaba Cloud official " +"CLI OAuth application." +msgstr "" +"1. El navegador puede mostrar official-cli; es la aplicación OAuth " +"oficial de la CLI de Alibaba Cloud." #: src/iac_code/services/providers/aliyun_oauth.py:302 -msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgid "" +"2. If assignment is required, assign the RAM user or RAM role that is " +"signed in. User groups are not supported." msgstr "" -"2. Si se requiere asignación, asigne el usuario RAM o el rol RAM que tiene la sesión iniciada. Los grupos de usuarios" -" no son compatibles." +"2. Si se requiere asignación, asigne el usuario RAM o el rol RAM que " +"tiene la sesión iniciada. Los grupos de usuarios no son compatibles." #: src/iac_code/services/providers/aliyun_oauth.py:306 msgid "" -"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " -"out of Alibaba Cloud and sign in again." +"3. After assignment, close the old authorization page and run OAuth Login" +" (Browser) again. If it still fails, sign out of Alibaba Cloud and sign " +"in again." msgstr "" -"3. Después de la asignación, cierre la página de autorización antigua y ejecute de nuevo Inicio de sesión OAuth " -"(navegador). Si sigue fallando, cierre sesión en Alibaba Cloud e iníciela de nuevo." +"3. Después de la asignación, cierre la página de autorización antigua y " +"ejecute de nuevo Inicio de sesión OAuth (navegador). Si sigue fallando, " +"cierre sesión en Alibaba Cloud e iníciela de nuevo." #: src/iac_code/services/providers/aliyun_oauth.py:310 -msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgid "" +"4. STS credentials refresh when possible until Alibaba Cloud expires " +"them. If refresh fails, run /auth again." msgstr "" -"4. Las credenciales STS se actualizan cuando es posible hasta que Alibaba Cloud las caduque. Si la actualización " -"falla, ejecute /auth de nuevo." +"4. Las credenciales STS se actualizan cuando es posible hasta que Alibaba" +" Cloud las caduque. Si la actualización falla, ejecute /auth de nuevo." #: src/iac_code/services/providers/aliyun_oauth.py:313 msgid "Press Esc to cancel while waiting." @@ -1407,19 +1579,25 @@ msgstr "Pulse Esc para cancelar la espera." #: src/iac_code/services/providers/aliyun_oauth.py:321 msgid "" -"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" -" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " -"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to " +"assign the official-cli application, assign it to the exact RAM user or " +"RAM role currently signed in. User groups are not supported. Then close " +"the old authorization page, sign out of Alibaba Cloud and sign in again " +"if needed, and run /auth to choose OAuth Login (Browser) again." msgstr "" -"Se agotó el tiempo esperando el callback OAuth. Si Alibaba Cloud le pidió asignar la aplicación official-cli, " -"asígnela al usuario RAM o al rol RAM exacto que tiene la sesión iniciada. Los grupos de usuarios no son compatibles. " -"Después cierre la página de autorización antigua, cierre la sesión de Alibaba Cloud e iníciela de nuevo si es " -"necesario, y ejecute /auth para elegir de nuevo Inicio de sesión OAuth (navegador)." +"Se agotó el tiempo esperando el callback OAuth. Si Alibaba Cloud le pidió" +" asignar la aplicación official-cli, asígnela al usuario RAM o al rol RAM" +" exacto que tiene la sesión iniciada. Los grupos de usuarios no son " +"compatibles. Después cierre la página de autorización antigua, cierre la " +"sesión de Alibaba Cloud e iníciela de nuevo si es necesario, y ejecute " +"/auth para elegir de nuevo Inicio de sesión OAuth (navegador)." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." -msgstr "La habilidad '{name}' está deshabilitada. Ejecute /skills para habilitarla." +msgstr "" +"La habilidad '{name}' está deshabilitada. Ejecute /skills para " +"habilitarla." #: src/iac_code/skills/skill_tool.py:137 #, python-brace-format @@ -1436,10 +1614,12 @@ msgid "Skill disabled: {name}" msgstr "Habilidad deshabilitada: {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." +msgid "" +"Review changed code for reuse, quality, and efficiency, then fix issues " +"found." msgstr "" -"Revise el código modificado en cuanto a reutilización, calidad y eficiencia; a continuación, corrija los problemas " -"detectados." +"Revise el código modificado en cuanto a reutilización, calidad y " +"eficiencia; a continuación, corrija los problemas detectados." #: src/iac_code/tools/edit_file.py:116 msgid "Edit" @@ -1537,8 +1717,9 @@ msgstr "La URL no puede estar vacía." #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Invalid URL: missing scheme (e.g. http:// or " -"https://). Got: {url}URL no válida: falta el esquema (p. ej. http:// o https://). Recibido: {url}" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Invalid" +" URL: missing scheme (e.g. http:// or https://). Got: {url}URL no válida:" +" falta el esquema (p. ej. http:// o https://). Recibido: {url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1647,7 +1828,8 @@ msgstr "Regla(s) de denegación coincidente(s): {}" msgid "matched ask rule(s): {}" msgstr "Regla(s) de consulta coincidente(s): {}" -#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 +#: src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "Regla(s) de permiso coincidente(s): {}" @@ -1704,7 +1886,8 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "Llamando a {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 +#: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Llamada correcta" @@ -1717,7 +1900,8 @@ msgstr "Respuesta recibida ({count} líneas)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 +#: src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "Ejecutando {action}..." @@ -1923,8 +2107,9 @@ msgstr "Se encontraron {count} documentos (total {total})" #: src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py:143 msgid "Use web_fetch tool to read full document content if needed." msgstr "" -"Use web_fetch tool to read full document content if needed.Use web_fetch tool to read full document content if " -"needed.Si es necesario, utilice la herramienta web_fetch para leer el documento completo." +"Use web_fetch tool to read full document content if needed.Use web_fetch " +"tool to read full document content if needed.Si es necesario, utilice la " +"herramienta web_fetch para leer el documento completo." #: src/iac_code/tools/cloud/aliyun/ros_stack.py:143 msgid "ROS Stack" @@ -1946,27 +2131,40 @@ msgid "" "Context:\n" "{context}" msgstr "" -"Error de sintaxis YAML en la plantilla (línea {line}, columna {col}): {problem}\n" +"Error de sintaxis YAML en la plantilla (línea {line}, columna {col}): " +"{problem}\n" "Contexto:\n" "{context}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:66 #, python-brace-format msgid "Template JSON syntax error (line {line}, column {col}): {msg}" -msgstr "Error de sintaxis JSON en la plantilla (línea {line}, columna {col}): {msg}" +msgstr "" +"Error de sintaxis JSON en la plantilla (línea {line}, columna {col}): " +"{msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "Template {fmt} parse result is not an object (dict), please check the template format" -msgstr "El resultado del análisis {fmt} de la plantilla no es un objeto (dict), por favor verifique el formato de la plantilla" +msgid "" +"Template {fmt} parse result is not an object (dict), please check the " +"template format" +msgstr "" +"El resultado del análisis {fmt} de la plantilla no es un objeto (dict), " +"por favor verifique el formato de la plantilla" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" -msgstr "Falta ROSTemplateFormatVersion en la plantilla (las plantillas ROS deben incluir este campo, ej. '2015-09-01')" +msgid "" +"Template is missing ROSTemplateFormatVersion (ROS templates must include " +"this field, e.g. '2015-09-01')" +msgstr "" +"Falta ROSTemplateFormatVersion en la plantilla (las plantillas ROS deben " +"incluir este campo, ej. '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" -msgstr "Falta Resources en la plantilla (las plantillas ROS deben incluir Resources)" +msgstr "" +"Falta Resources en la plantilla (las plantillas ROS deben incluir " +"Resources)" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:112 #, python-brace-format @@ -1975,8 +2173,12 @@ msgstr "Resources debe ser un objeto (dict), el tipo actual es {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "Resource '{name}' definition must be an object (dict), current type is {type}" -msgstr "La definición del recurso '{name}' debe ser un objeto (dict), el tipo actual es {type}" +msgid "" +"Resource '{name}' definition must be an object (dict), current type is " +"{type}" +msgstr "" +"La definición del recurso '{name}' debe ser un objeto (dict), el tipo " +"actual es {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 #, python-brace-format @@ -1986,11 +2188,17 @@ msgstr "Al recurso '{name}' le falta el campo Type" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:129 #, python-brace-format msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" -msgstr "El recurso '{name}' tiene el tipo incorrecto '{wrong}', debería ser '{correct}'" +msgstr "" +"El recurso '{name}' tiene el tipo incorrecto '{wrong}', debería ser " +"'{correct}'" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "Template structure validation found the following issues, please fix and retry:" -msgstr "La validación de la estructura de la plantilla encontró los siguientes problemas, por favor corrija y reintente:" +msgid "" +"Template structure validation found the following issues, please fix and " +"retry:" +msgstr "" +"La validación de la estructura de la plantilla encontró los siguientes " +"problemas, por favor corrija y reintente:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 #, python-brace-format @@ -2010,28 +2218,30 @@ msgstr "Notas de la versión" msgid "Run {} to update." msgstr "Ejecuta {} para actualizar." -#: src/iac_code/ui/banner.py:105 +#: src/iac_code/ui/banner.py:110 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "Su asistente de infraestructura como código con IA" -#: src/iac_code/ui/banner.py:131 +#: src/iac_code/ui/banner.py:144 msgid "Welcome back" msgstr "Bienvenido de nuevo" -#: src/iac_code/ui/banner.py:148 +#: src/iac_code/ui/banner.py:161 msgid "Debug mode" msgstr "Modo depuración" -#: src/iac_code/ui/banner.py:149 +#: src/iac_code/ui/banner.py:162 msgid "Log file" msgstr "Archivo de registro" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 +#: src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "Razonamiento durante {seconds:.1f} s" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 +#: src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o para expandir)" @@ -2117,99 +2327,118 @@ msgstr "No, siempre denegar \"{rule}\" (esta sesión)" msgid "No, always reject this tool" msgstr "No, rechazar siempre esta herramienta" -#: src/iac_code/ui/repl.py:406 +#: src/iac_code/ui/repl.py:412 msgid "Press Ctrl+C again to exit." msgstr "Pulse Ctrl+C de nuevo para salir." -#: src/iac_code/ui/repl.py:431 +#: src/iac_code/ui/repl.py:437 msgid "Interrupted." msgstr "Interrumpido." -#: src/iac_code/ui/repl.py:468 -msgid "Goodbye!" -msgstr "¡Hasta luego!" - -#: src/iac_code/ui/repl.py:469 -msgid "Resume this session with:" -msgstr "Para reanudar esta sesión, ejecute:" - -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:496 msgid "Update now" msgstr "Actualizar ahora" -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:498 msgid "Run the shown update command and exit when it succeeds." -msgstr "Ejecuta el comando de actualización mostrado y sale cuando finalice correctamente." +msgstr "" +"Ejecuta el comando de actualización mostrado y sale cuando finalice " +"correctamente." -#: src/iac_code/ui/repl.py:499 +#: src/iac_code/ui/repl.py:501 msgid "Skip" msgstr "Omitir" -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:503 msgid "Continue with the current version for this session." msgstr "Continuar con la versión actual durante esta sesión." -#: src/iac_code/ui/repl.py:504 +#: src/iac_code/ui/repl.py:506 msgid "Skip until next version" msgstr "Omitir hasta la siguiente versión" -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:508 msgid "Hide this update until a newer version is available." -msgstr "Ocultar esta actualización hasta que haya una versión más nueva disponible." +msgstr "" +"Ocultar esta actualización hasta que haya una versión más nueva " +"disponible." -#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 +#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 msgid "Update command failed. Continuing with the current version." msgstr "El comando de actualización falló. Se continuará con la versión actual." -#: src/iac_code/ui/repl.py:530 +#: src/iac_code/ui/repl.py:532 msgid "Update completed. Restart iac-code to continue." msgstr "Actualización completada. Reinicia iac-code para continuar." -#: src/iac_code/ui/repl.py:568 +#: src/iac_code/ui/repl.py:570 msgid "No image in clipboard." msgstr "No hay ninguna imagen en el portapapeles." -#: src/iac_code/ui/repl.py:754 +#: src/iac_code/ui/repl.py:756 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:759 +#: src/iac_code/ui/repl.py:761 msgid "Shell command support is unavailable." msgstr "La compatibilidad con comandos de shell no está disponible." -#: src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." -msgstr "Habilidad desconocida: ${name}. Escribe / para listar comandos y habilidades." +msgstr "" +"Habilidad desconocida: ${name}. Escribe / para listar comandos y " +"habilidades." -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:830 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" -"Unknown command: /{name}. Type /help for available commands.Unknown command: /{name}. Type /help for available " -"commands.Comando desconocido: /{name}. Escriba /help para ver los comandos disponibles." +"Unknown command: /{name}. Type /help for available commands.Unknown " +"command: /{name}. Type /help for available commands.Comando desconocido: " +"/{name}. Escriba /help para ver los comandos disponibles." -#: src/iac_code/ui/repl.py:833 +#: src/iac_code/ui/repl.py:835 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ solo invoca habilidades. Usa /{name} en su lugar." -#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 +#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 #, python-brace-format msgid "Command error: {error}" msgstr "Error de comando: {error}" -#: src/iac_code/ui/repl.py:862 +#: src/iac_code/ui/repl.py:864 #, python-brace-format msgid "Command has no handler: {name}" msgstr "El comando no tiene controlador: {name}" -#: src/iac_code/ui/repl.py:1167 +#: src/iac_code/ui/repl.py:1126 +msgid "Goodbye!" +msgstr "¡Hasta luego!" + +#: src/iac_code/ui/repl.py:1127 +msgid "Resume this session with:" +msgstr "Para reanudar esta sesión, ejecute:" + +#: src/iac_code/ui/repl.py:1130 +msgid "Session ID" +msgstr "ID de sesión" + +#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sesión no encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1233 +msgid "Session name: " +msgstr "Nombre de sesión: " + +#: src/iac_code/ui/repl.py:1239 +msgid "Session name cannot be empty." +msgstr "El nombre de sesión no puede estar vacío." + +#: src/iac_code/ui/repl.py:1251 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2220,31 +2449,43 @@ msgstr "" "Para reanudar, ejecute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1287 +#: src/iac_code/ui/repl.py:1257 +msgid "Multiple sessions match. Resume one by ID:" +msgstr "Varias sesiones coinciden. Reanuda una por ID:" + +#: src/iac_code/ui/repl.py:1370 msgid "This conversation is from a different directory." msgstr "Esta conversación procede de otro directorio." -#: src/iac_code/ui/repl.py:1289 +#: src/iac_code/ui/repl.py:1372 msgid "To resume, run:" msgstr "Para reanudar, ejecute:" -#: src/iac_code/ui/repl.py:1294 +#: src/iac_code/ui/repl.py:1377 msgid "(Command copied to clipboard)" msgstr "(Comando copiado al portapapeles)" -#: src/iac_code/ui/repl.py:1451 +#: src/iac_code/ui/repl.py:1534 #, python-brace-format -msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." -msgstr "El modelo actual {model} no admite entrada de imágenes. Usa /model para cambiar a un modelo con capacidad de visión." +msgid "" +"Current model {model} does not support image input. Use /model to switch " +"to a vision-capable model." +msgstr "" +"El modelo actual {model} no admite entrada de imágenes. Usa /model para " +"cambiar a un modelo con capacidad de visión." -#: src/iac_code/ui/repl.py:1460 +#: src/iac_code/ui/repl.py:1543 #, python-brace-format msgid "Image error: {err}" msgstr "Error de imagen: {err}" -#: src/iac_code/ui/repl.py:1477 -msgid "Failed to persist image to cache; it will only exist in memory for this turn." -msgstr "No se pudo persistir la imagen en la caché; solo existirá en memoria durante este turno." +#: src/iac_code/ui/repl.py:1560 +msgid "" +"Failed to persist image to cache; it will only exist in memory for this " +"turn." +msgstr "" +"No se pudo persistir la imagen en la caché; solo existirá en memoria " +"durante este turno." #: src/iac_code/ui/spinner.py:52 msgid "Processing" @@ -2334,87 +2575,87 @@ msgstr "Escriba para buscar archivos..." msgid "No matching files" msgstr "No hay archivos coincidentes" -#: src/iac_code/ui/dialogs/resume_picker.py:114 +#: src/iac_code/ui/dialogs/resume_picker.py:116 msgid "Search..." msgstr "Buscar…" -#: src/iac_code/ui/dialogs/resume_picker.py:359 +#: src/iac_code/ui/dialogs/resume_picker.py:374 msgid "Resume Session" msgstr "Reanudar sesión" -#: src/iac_code/ui/dialogs/resume_picker.py:369 +#: src/iac_code/ui/dialogs/resume_picker.py:384 msgid "No sessions found" msgstr "No se encontraron sesiones" -#: src/iac_code/ui/dialogs/resume_picker.py:427 +#: src/iac_code/ui/dialogs/resume_picker.py:444 msgid "show current dir" msgstr "mostrar directorio actual" -#: src/iac_code/ui/dialogs/resume_picker.py:429 +#: src/iac_code/ui/dialogs/resume_picker.py:446 msgid "show all projects" msgstr "mostrar todos los proyectos" -#: src/iac_code/ui/dialogs/resume_picker.py:432 +#: src/iac_code/ui/dialogs/resume_picker.py:449 msgid "show all branches" msgstr "mostrar todas las ramas" -#: src/iac_code/ui/dialogs/resume_picker.py:434 +#: src/iac_code/ui/dialogs/resume_picker.py:451 msgid "only show current branch" msgstr "mostrar solo la rama actual" -#: src/iac_code/ui/dialogs/resume_picker.py:435 +#: src/iac_code/ui/dialogs/resume_picker.py:452 msgid "preview" msgstr "vista previa" -#: src/iac_code/ui/dialogs/resume_picker.py:436 +#: src/iac_code/ui/dialogs/resume_picker.py:453 msgid "Type to search" msgstr "Escriba para buscar" -#: src/iac_code/ui/dialogs/resume_picker.py:437 +#: src/iac_code/ui/dialogs/resume_picker.py:454 msgid "cancel" msgstr "cancelar" -#: src/iac_code/ui/dialogs/resume_picker.py:552 +#: src/iac_code/ui/dialogs/resume_picker.py:569 #, python-brace-format msgid "{n} more line{s}" msgstr "{n} línea{s} más" -#: src/iac_code/ui/dialogs/resume_picker.py:566 +#: src/iac_code/ui/dialogs/resume_picker.py:583 #, python-brace-format msgid "{n} message{s}" msgstr "{n} mensaje{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:580 +#: src/iac_code/ui/dialogs/resume_picker.py:597 msgid "resume" msgstr "reanudar" -#: src/iac_code/ui/dialogs/resume_picker.py:584 +#: src/iac_code/ui/dialogs/resume_picker.py:601 msgid "back" msgstr "atrás" -#: src/iac_code/ui/dialogs/resume_picker.py:589 +#: src/iac_code/ui/dialogs/resume_picker.py:606 msgid "scroll" msgstr "desplazar" -#: src/iac_code/ui/dialogs/resume_picker.py:608 +#: src/iac_code/ui/dialogs/resume_picker.py:625 msgid "(empty session)" msgstr "(sesión vacía)" -#: src/iac_code/ui/dialogs/resume_picker.py:728 +#: src/iac_code/ui/dialogs/resume_picker.py:745 msgid "just now" msgstr "ahora mismo" -#: src/iac_code/ui/dialogs/resume_picker.py:731 +#: src/iac_code/ui/dialogs/resume_picker.py:748 #, python-brace-format msgid "{n} minute{s} ago" msgstr "hace {n} minuto{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:734 +#: src/iac_code/ui/dialogs/resume_picker.py:751 #, python-brace-format msgid "{n} hour{s} ago" msgstr "hace {n} hora{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:736 +#: src/iac_code/ui/dialogs/resume_picker.py:753 #, python-brace-format msgid "{n} day{s} ago" msgstr "hace {n} día{s}" @@ -2434,8 +2675,12 @@ msgstr "{current} de {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" -msgstr "{count} habilidades - Espacio para alternar, Enter para guardar, Tab para ordenar, Esc para cancelar" +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "" +"{count} habilidades - Espacio para alternar, Enter para guardar, Tab para" +" ordenar, Esc para cancelar" #: src/iac_code/ui/dialogs/skills_picker.py:171 #, python-brace-format @@ -2521,8 +2766,12 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code en Windows requiere Git for Windows." #: src/iac_code/utils/platform.py:41 -msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." -msgstr "Si está instalado pero no está en el PATH, establezca la variable de entorno IAC_CODE_GIT_BASH_PATH." +msgid "" +"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " +"variable." +msgstr "" +"Si está instalado pero no está en el PATH, establezca la variable de " +"entorno IAC_CODE_GIT_BASH_PATH." #: src/iac_code/utils/platform.py:44 msgid "To install:" @@ -2533,8 +2782,12 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " Opción 1 - winget (requiere acceso a github.com):" #: src/iac_code/utils/platform.py:52 -msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" -msgstr " Opción 2 - si no puedes acceder a github.com, ejecuta esto para instalar vía npmmirror:" +msgid "" +" Option 2 - if you cannot reach github.com, run this to install via " +"npmmirror:" +msgstr "" +" Opción 2 - si no puedes acceder a github.com, ejecuta esto para " +"instalar vía npmmirror:" #~ msgid "toggle preview" #~ msgstr "alternar vista previa" @@ -2557,8 +2810,14 @@ msgstr " Opción 2 - si no puedes acceder a github.com, ejecuta esto para insta #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." -#~ msgstr "El proveedor LLM está bloqueado por '{source}'. Para cambiar, modifique llm_source en settings.yml." +#~ msgid "" +#~ "LLM provider is locked by '{source}'." +#~ " To change, modify llm_source in " +#~ "settings.yml." +#~ msgstr "" +#~ "El proveedor LLM está bloqueado por " +#~ "'{source}'. Para cambiar, modifique llm_source" +#~ " en settings.yml." #~ msgid "Provider switched: {status}" #~ msgstr "Provider cambiado: {status}" @@ -2575,6 +2834,16 @@ msgstr " Opción 2 - si no puedes acceder a github.com, ejecuta esto para insta #~ msgid "Cache create" #~ msgstr "Creación de caché" -#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" -#~ msgstr "{count} habilidades - Espacio para alternar, Enter para guardar, / para buscar, t para ordenar, Esc para cancelar" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "" +#~ "{count} habilidades - Espacio para " +#~ "alternar, Enter para guardar, / para " +#~ "buscar, t para ordenar, Esc para " +#~ "cancelar" + +#~ msgid "Resume a session by ID" +#~ msgstr "Reanudar una sesión por ID" 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 2ce3c04..0782765 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 21:26+0800\n" +"POT-Creation-Date: 2026-06-02 22:08+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: fr\n" @@ -20,86 +20,133 @@ msgstr "" #: src/iac_code/config.py:164 #, python-brace-format msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}" -msgstr "Valeur IAC_CODE_PROVIDER invalide : {!r}. Valeurs valides (insensible à la casse) : {}" +msgstr "" +"Valeur IAC_CODE_PROVIDER invalide : {!r}. Valeurs valides (insensible à " +"la casse) : {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." +msgid "" +"Unix domain socket transport is not supported on Windows. Use --transport" +" http or --transport stdio instead." msgstr "" -"Le transport par socket de domaine Unix n'est pas pris en charge sous Windows. Utilisez --transport http ou " -"--transport stdio à la place." +"Le transport par socket de domaine Unix n'est pas pris en charge sous " +"Windows. Utilisez --transport http ou --transport stdio à la place." + +#: src/iac_code/acp/server.py:413 src/iac_code/acp/server.py:429 +#: src/iac_code/acp/server.py:456 +msgid "Session not found" +msgstr "Session introuvable" + +#: src/iac_code/acp/server.py:416 +#, python-brace-format +msgid "Session name is ambiguous. Candidates: {candidates}" +msgstr "Le nom de session est ambigu. Candidats : {candidates}" + +#: src/iac_code/acp/server.py:434 src/iac_code/acp/server.py:708 +#, python-brace-format +msgid "Session belongs to another project. Run: {hint}" +msgstr "La session appartient à un autre projet. Exécutez : {hint}" -#: src/iac_code/acp/slash_registry.py:44 +#: src/iac_code/acp/slash_registry.py:46 #, python-brace-format -msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" -msgstr "La commande « /{cmd_name} » n’est pas prise en charge via ACP. Commandes prises en charge : {supported}" +msgid "" +"Command '/{cmd_name}' is not supported over ACP. Supported commands: " +"{supported}" +msgstr "" +"La commande « /{cmd_name} » n’est pas prise en charge via ACP. Commandes " +"prises en charge : {supported}" -#: src/iac_code/acp/slash_registry.py:58 +#: src/iac_code/acp/slash_registry.py:62 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "Gestionnaire de la commande « /{cmd_name} » non implémenté." -#: src/iac_code/acp/slash_registry.py:70 +#: src/iac_code/acp/slash_registry.py:74 #, python-brace-format msgid "Compaction failed: {error}" msgstr "Échec de la compaction : {error}" -#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:77 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "Rien à compacter : la conversation est vide." -#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." +msgid "" +"Conversation too short to compact: all messages are within the recent " +"{turns}-turn preservation window." msgstr "" -"Conversation trop courte pour être compactée : tous les messages se situent dans la fenêtre de conservation des " -"{turns} derniers tours." +"Conversation trop courte pour être compactée : tous les messages se " +"situent dans la fenêtre de conservation des {turns} derniers tours." -#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:84 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "Échec de la compaction. Consultez les journaux pour plus de détails." -#: src/iac_code/acp/slash_registry.py:85 +#: src/iac_code/acp/slash_registry.py:89 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" -msgstr "Contexte compacté : {original} → {compacted} tokens (réduction {percent}). Utilisation du contexte : {usage}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent} reduction)." +" Context usage: {usage}" +msgstr "" +"Contexte compacté : {original} → {compacted} tokens (réduction " +"{percent}). Utilisation du contexte : {usage}" -#: src/iac_code/acp/slash_registry.py:99 +#: src/iac_code/acp/slash_registry.py:103 #, python-brace-format msgid "Clear failed: {error}" msgstr "Échec de l’effacement : {error}" -#: src/iac_code/acp/slash_registry.py:100 +#: src/iac_code/acp/slash_registry.py:104 msgid "Conversation history cleared." msgstr "Historique de conversation effacé." -#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:120 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "Journalisation debug activée. Fichier de journal : {path}" -#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "Journalisation debug désactivée." -#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "Journalisation debug activée. Fichier de journal : {path}" -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:129 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "Journalisation debug désactivée." -#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:131 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "Utilisation : /debug [on|off]" -#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +#: src/iac_code/acp/slash_registry.py:136 src/iac_code/commands/memory.py:84 msgid "Memory manager is unavailable." msgstr "Le gestionnaire de mémoire est indisponible." -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 -#: src/iac_code/ui/repl.py:807 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +msgid "Usage: /rename " +msgstr "Utilisation : /rename " + +#: src/iac_code/acp/slash_registry.py:152 +msgid "Rename is only available after a session is created." +msgstr "Le renommage n'est disponible qu'après la création d'une session." + +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#, python-brace-format +msgid "Session is already named {name}" +msgstr "La session porte déjà le nom {name}" + +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#, python-brace-format +msgid "Renamed session to {name}" +msgstr "Session renommée en {name}" + +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 msgid "Permission denied." msgstr "Permission refusée." @@ -116,7 +163,8 @@ msgstr "Explorer" msgid "Plan" msgstr "Plan" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 +#: src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "Agent" @@ -168,14 +216,17 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-" +"code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " Correction : exécutez iac-code puis tapez /auth\n" -" ou définissez la variable d’environnement : IAC_CODE_API_KEY=\n" -" Documentation : https://aliyun.github.io/iac-code/fr/docs/configuration/authentication\n" +" ou définissez la variable d’environnement : IAC_CODE_API_KEY=\n" +" Documentation : https://aliyun.github.io/iac-" +"code/fr/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -188,7 +239,9 @@ msgstr "Installation de Git for Windows via npmmirror..." #: src/iac_code/cli/install_git_bash.py:59 msgid "powershell.exe was not found on PATH; cannot run installer." -msgstr "powershell.exe est introuvable dans PATH ; impossible d'exécuter l'installateur." +msgstr "" +"powershell.exe est introuvable dans PATH ; impossible d'exécuter " +"l'installateur." #: src/iac_code/cli/install_git_bash.py:66 #, python-brace-format @@ -197,11 +250,12 @@ msgstr "L'installation a échoué (PowerShell est sorti avec le code {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " -"non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may " +"have been cancelled or the installer used a non-standard path." msgstr "" -"L'installateur s'est terminé mais bash.exe est introuvable dans les emplacements habituels ; UAC a peut-être été " -"annulé ou l'installateur a utilisé un chemin non standard." +"L'installateur s'est terminé mais bash.exe est introuvable dans les " +"emplacements habituels ; UAC a peut-être été annulé ou l'installateur a " +"utilisé un chemin non standard." #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -224,9 +278,14 @@ msgstr "Installer Git for Windows via le miroir npmmirror (Windows uniquement)." msgid "YAML config file containing A2A client options" msgstr "Fichier de configuration YAML contenant les options client A2A" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" -msgstr "Les dépendances du client A2A sont manquantes. Installez-les avec : pip install 'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 +#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "" +"A2A client dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" +msgstr "" +"Les dépendances du client A2A sont manquantes. Installez-les avec : pip " +"install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 msgid "LLM model to use" @@ -244,7 +303,8 @@ msgstr "Format de sortie : text, json, stream-json" msgid "Maximum agent turns in headless mode" msgstr "Nombre maximal de tours d’agent en mode headless" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 +#: src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "Activer la journalisation debug" @@ -257,8 +317,8 @@ msgid "Show version and exit" msgstr "Afficher la version et quitter" #: src/iac_code/cli/main.py:89 -msgid "Resume a session by ID" -msgstr "Reprendre une session par identifiant" +msgid "Resume a session by ID or name" +msgstr "Reprendre une session par ID ou nom" #: src/iac_code/cli/main.py:90 msgid "Resume the most recent session" @@ -269,12 +329,20 @@ msgid "Install completion for the current shell." msgstr "Installer la complétion pour le shell actuel." #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "Show completion for the current shell, to copy it or customize the installation." -msgstr "Afficher la complétion pour le shell actuel afin de la copier ou de personnaliser l’installation." +msgid "" +"Show completion for the current shell, to copy it or customize the " +"installation." +msgstr "" +"Afficher la complétion pour le shell actuel afin de la copier ou de " +"personnaliser l’installation." #: src/iac_code/cli/main.py:110 -msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" -msgstr "Modèles de permissions d'outils à autoriser (séparés par des virgules), ex. 'bash(git *),write_file'*),write_file'" +msgid "" +"Comma-separated tool permission patterns to allow, e.g. 'bash(git " +"*),write_file'" +msgstr "" +"Modèles de permissions d'outils à autoriser (séparés par des virgules), " +"ex. 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 msgid "Comma-separated tool permission patterns to deny" @@ -283,8 +351,8 @@ msgstr "Modèles de permissions d'outils à refuser (séparés par des virgules) #: src/iac_code/cli/main.py:120 msgid "Permission mode: default, accept_edits, bypass_permissions, dont_ask" msgstr "" -"Permission mode: default, accept_edits, bypass_permissions, dont_askMode de permissions : default, accept_edits, " -"bypass_permissions, dont_ask" +"Permission mode: default, accept_edits, bypass_permissions, dont_askMode " +"de permissions : default, accept_edits, bypass_permissions, dont_ask" #: src/iac_code/cli/main.py:151 msgid "Error: --resume and --continue cannot be used together." @@ -320,27 +388,45 @@ msgid "YAML config file for A2A server options" msgstr "Fichier de configuration YAML pour les options du serveur A2A" #: src/iac_code/cli/main.py:584 -msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." -msgstr "Port du serveur HTTP. 41242 est la valeur par défaut d'iac-code inspirée de Gemini CLI, pas un port A2A enregistré." +msgid "" +"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " +"not a registered A2A port." +msgstr "" +"Port du serveur HTTP. 41242 est la valeur par défaut d'iac-code inspirée " +"de Gemini CLI, pas un port A2A enregistré." #: src/iac_code/cli/main.py:589 -msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" -msgstr "Transport A2A : http, stdio, unix, websocket, grpc, grpc-jsonrpc ou redis-streams" +msgid "" +"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " +"redis-streams" +msgstr "" +"Transport A2A : http, stdio, unix, websocket, grpc, grpc-jsonrpc ou " +"redis-streams" #: src/iac_code/cli/main.py:595 -msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." -msgstr "Expose les types de signal de thinking A2A ; répétez pour en fournir plusieurs. Valeurs : raw-thinking, tool-trace." +msgid "" +"Expose A2A thinking signal types; repeat for multiple. Values: raw-" +"thinking, tool-trace." +msgstr "" +"Expose les types de signal de thinking A2A ; répétez pour en fournir " +"plusieurs. Valeurs : raw-thinking, tool-trace." #: src/iac_code/cli/main.py:646 -msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" -msgstr "Les dépendances du serveur A2A sont manquantes. Installez-les avec : pip install 'iac-code[a2a]'" +msgid "" +"A2A server dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" +msgstr "" +"Les dépendances du serveur A2A sont manquantes. Installez-les avec : pip " +"install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "Envoie un prompt à un point de terminaison JSON-RPC A2A." -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 +#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 +#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "URL du point de terminaison JSON-RPC A2A" @@ -365,33 +451,48 @@ msgstr "Métadonnées du répertoire de travail à envoyer avec la requête" msgid "A2A context ID to continue" msgstr "ID de contexte A2A à poursuivre" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 +#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 +#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 +#: src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "Jeton Bearer pour les requêtes HTTP A2A" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 +#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 +#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 +#: src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "Nom d'utilisateur d'authentification basique pour les requêtes HTTP A2A" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 +#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 +#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 +#: src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "Mot de passe d'authentification basique pour les requêtes HTTP A2A" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 +#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 +#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 +#: src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "Clé d'API pour les requêtes HTTP A2A" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 +#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 +#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 +#: src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "Nom de l'en-tête HTTP pour la clé d'API A2A" @@ -427,8 +528,10 @@ msgstr "URL de base de l'agent A2A" msgid "Get an A2A task." msgstr "Récupère une tâche A2A." -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 +#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 +#: src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "ID de tâche A2A" @@ -476,7 +579,8 @@ msgstr "S'abonne à un flux d'événements de tâche A2A." msgid "Create an A2A task push notification config." msgstr "Crée une configuration de notifications push de tâche A2A." -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 +#: src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "ID de configuration push" @@ -540,81 +644,89 @@ msgstr "Répertoire pour les routes A2A persistées" msgid "Save the provided routes as a route snapshot" msgstr "Enregistre les routes fournies sous forme d'instantané de routes" -#: src/iac_code/commands/__init__.py:25 +#: src/iac_code/commands/__init__.py:26 msgid "Show available commands" msgstr "Afficher les commandes disponibles" -#: src/iac_code/commands/__init__.py:34 +#: src/iac_code/commands/__init__.py:35 msgid "Clear conversation history" msgstr "Effacer l’historique de conversation" -#: src/iac_code/commands/__init__.py:42 +#: src/iac_code/commands/__init__.py:43 msgid "Show or switch model" msgstr "Afficher ou changer de modèle" -#: src/iac_code/commands/__init__.py:51 +#: src/iac_code/commands/__init__.py:52 msgid "Show or switch thinking effort" msgstr "Afficher ou modifier l’effort de raisonnement" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compact conversation context" msgstr "Compacter le contexte de conversation" -#: src/iac_code/commands/__init__.py:62 +#: src/iac_code/commands/__init__.py:63 msgid "Compacting conversation" msgstr "Compactage de la conversation" -#: src/iac_code/commands/__init__.py:69 +#: src/iac_code/commands/__init__.py:70 msgid "Exit the application" msgstr "Quitter l’application" -#: src/iac_code/commands/__init__.py:78 +#: src/iac_code/commands/__init__.py:79 msgid "Authenticate with LLM provider" msgstr "S’authentifier auprès du fournisseur LLM" -#: src/iac_code/commands/__init__.py:87 +#: src/iac_code/commands/__init__.py:88 msgid "Toggle debug logging" msgstr "Activer ou désactiver la journalisation debug" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "View and manage persistent memories" msgstr "Afficher et gérer les mémoires persistantes" -#: src/iac_code/commands/__init__.py:98 +#: src/iac_code/commands/__init__.py:99 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "Resume a previous session" msgstr "Reprendre une session précédente" -#: src/iac_code/commands/__init__.py:107 +#: src/iac_code/commands/__init__.py:108 msgid "[conversation id or search term]" msgstr "[identifiant de conversation ou terme de recherche]" -#: src/iac_code/commands/__init__.py:114 +#: src/iac_code/commands/__init__.py:115 +msgid "Rename the current session" +msgstr "Renommer la session actuelle" + +#: src/iac_code/commands/__init__.py:124 msgid "Manage skills" msgstr "Gérer les compétences" -#: src/iac_code/commands/__init__.py:122 +#: src/iac_code/commands/__init__.py:132 msgid "Show current session status" msgstr "Afficher l’état actuel de la session" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Naviguer" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 -#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmer" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 -#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 -#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 +#: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 +#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "Retour" @@ -626,8 +738,9 @@ msgstr "Conserver" msgid "Re-enter" msgstr "Saisir à nouveau" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 +#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 +#: src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (actuel)" @@ -664,17 +777,20 @@ msgstr "Configurer le service cloud IaC" msgid "Select configuration type" msgstr "Sélectionner le type de configuration" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 +#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 +#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "Authentification annulée" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 +#: src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "Sélectionner le fournisseur — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 +#: src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "Tiers" @@ -770,8 +886,9 @@ msgstr "Sélectionner le fournisseur cloud" msgid "Credential" msgstr "Identifiants" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 -#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 +#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Région" @@ -883,8 +1000,9 @@ msgstr "Sélectionner le type d’identifiants" #: src/iac_code/commands/auth.py:1510 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" +"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:1517 msgid "Configure Alibaba Cloud region" @@ -893,8 +1011,9 @@ msgstr "Configurer la région Alibaba Cloud" #: src/iac_code/commands/auth.py:1543 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "" -"Configured: Alibaba Cloud region saved to ~/.iac-codeConfigured: Alibaba Cloud region saved to ~/.iac-" -"codeConfiguration effectuée : région Alibaba Cloud enregistrée dans ~/.iac-code" +"Configured: Alibaba Cloud region saved to ~/.iac-codeConfigured: Alibaba " +"Cloud region saved to ~/.iac-codeConfiguration effectuée : région Alibaba" +" Cloud enregistrée dans ~/.iac-code" #: src/iac_code/commands/compact.py:12 msgid "Compact command requires a context." @@ -910,10 +1029,12 @@ msgstr "Aucune boucle d’agent active." #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent_display} " +"reduction). Context usage: {usage_display}" msgstr "" -"Contexte compacté : {original} → {compacted} tokens (réduction {percent_display}). Utilisation du contexte : " -"{usage_display}" +"Contexte compacté : {original} → {compacted} tokens (réduction " +"{percent_display}). Utilisation du contexte : {usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -923,7 +1044,8 @@ msgstr "La commande debug nécessite un contexte." msgid "No active session." msgstr "Aucune session active." -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 +#: src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "Aucun fournisseur configuré. Exécutez d’abord /auth." @@ -1017,10 +1139,12 @@ msgstr "Mémoire '{name}' supprimée." #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." +msgid "" +"Model is managed by '{source}'. To change model, modify it in {source} or" +" switch provider via /auth." msgstr "" -"Le modèle est géré par '{source}'. Pour changer le modèle, modifiez-le dans {source} ou changez de fournisseur via " -"/auth." +"Le modèle est géré par '{source}'. Pour changer le modèle, modifiez-le " +"dans {source} ou changez de fournisseur via /auth." #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1037,23 +1161,36 @@ msgstr "Modèle actuel : {model}" msgid "Kept model as {model}" msgstr "Modèle conservé : {model}" -#: src/iac_code/commands/resume.py:21 +#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +msgid "Rename is only available in interactive mode." +msgstr "Le renommage n'est disponible qu'en mode interactif." + +#: src/iac_code/commands/rename.py:30 +msgid "Rename cancelled" +msgstr "Renommage annulé" + +#: src/iac_code/commands/resume.py:22 msgid "Resume is only available in interactive mode." msgstr "/resume n’est disponible qu’en mode interactif." -#: src/iac_code/commands/resume.py:27 +#: src/iac_code/commands/resume.py:28 msgid "Resume is unavailable: session index not initialised." msgstr "Reprise indisponible : l’index des sessions n’est pas initialisé." -#: src/iac_code/commands/resume.py:32 +#: src/iac_code/commands/resume.py:33 src/iac_code/commands/resume.py:36 #, python-brace-format msgid "Session not found: {arg}" msgstr "Session introuvable : {arg}" -#: src/iac_code/commands/resume.py:47 +#: src/iac_code/commands/resume.py:52 src/iac_code/commands/resume.py:68 msgid "Resume cancelled" msgstr "Reprise annulée" +#: src/iac_code/commands/resume.py:55 +#, python-brace-format +msgid "Unable to resolve session: {arg}" +msgstr "Impossible de résoudre la session : {arg}" + #: src/iac_code/commands/skills.py:14 msgid "Skills management is only available in interactive mode." msgstr "La gestion des compétences n'est disponible qu'en mode interactif." @@ -1078,7 +1215,8 @@ msgstr "La commande status nécessite un contexte REPL." msgid "Status is only available in interactive mode." msgstr "status n’est disponible qu’en mode interactif." -#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:136 +#: src/iac_code/ui/banner.py:138 msgid "Session" msgstr "Session" @@ -1086,7 +1224,8 @@ msgstr "Session" msgid "Provider" msgstr "Fournisseur" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "non configuré" @@ -1187,9 +1326,10 @@ msgstr "Interrompu !" #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." msgstr "" -"Cannot determine provider for model: {model}. Run /auth to configure.Cannot determine provider for model: {model}. " -"Run /auth to configure.Impossible de déterminer le fournisseur pour le modèle : {model}. Exécutez /auth pour " -"configurer." +"Cannot determine provider for model: {model}. Run /auth to " +"configure.Cannot determine provider for model: {model}. Run /auth to " +"configure.Impossible de déterminer le fournisseur pour le modèle : " +"{model}. Exécutez /auth pour configurer." #: src/iac_code/providers/manager.py:95 #, python-brace-format @@ -1198,26 +1338,34 @@ msgstr "Clé de fournisseur inconnue : '{key}'. Exécutez /auth pour configurer. #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." -msgstr "Aucune clé API configurée pour le fournisseur '{provider}' (modèle : {model}). Exécutez /auth pour configurer." +msgid "" +"No API key configured for provider '{provider}' (model: {model}). Run " +"/auth to configure." +msgstr "" +"Aucune clé API configurée pour le fournisseur '{provider}' (modèle : " +"{model}). Exécutez /auth pour configurer." #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " -"endpoints require a /v1 suffix (e.g. {base_url}/v1)." +"API returned no data. Please check that your API Base URL is correct " +"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " +"suffix (e.g. {base_url}/v1)." msgstr "" -"L’API n’a renvoyé aucune donnée. Vérifiez que votre API Base URL est correcte (actuelle : {base_url}). De nombreux " -"points de terminaison compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." +"L’API n’a renvoyé aucune donnée. Vérifiez que votre API Base URL est " +"correcte (actuelle : {base_url}). De nombreux points de terminaison " +"compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" -"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." +"API returned an invalid response. Please check that your API Base URL is " +"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" +" /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"L’API a renvoyé une réponse invalide. Vérifiez que votre API Base URL est correcte (actuelle : {base_url}). De " -"nombreux points de terminaison compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." +"L’API a renvoyé une réponse invalide. Vérifiez que votre API Base URL est" +" correcte (actuelle : {base_url}). De nombreux points de terminaison " +"compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1298,22 +1446,36 @@ msgstr "Compatible Anthropic" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " +"support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " -"settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " +"mode (remove 'llm_source: qwenpaw' from settings.yml)." msgstr "" -"[Mode QwenPaw] Fournisseur inconnu '{provider_id}'. iac-code ne prend pas en charge ce fournisseur.\n" +"[Mode QwenPaw] Fournisseur inconnu '{provider_id}'. iac-code ne prend pas" +" en charge ce fournisseur.\n" "IDs de fournisseurs QwenPaw pris en charge : {supported_ids}\n" -"Solution : passez à un fournisseur pris en charge dans QwenPaw, ou désactivez le mode QwenPaw (supprimez 'llm_source:" -" qwenpaw' de settings.yml)." +"Solution : passez à un fournisseur pris en charge dans QwenPaw, ou " +"désactivez le mode QwenPaw (supprimez 'llm_source: qwenpaw' de " +"settings.yml)." + +#: src/iac_code/services/session_metadata.py:52 +#, python-brace-format +msgid "Session name must match {pattern}" +msgstr "Le nom de session doit correspondre à {pattern}" + +#: src/iac_code/services/session_storage.py:241 +#, python-brace-format +msgid "Session name already exists in this project: {name}" +msgstr "Le nom de session existe déjà dans ce projet : {name}" #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "--permission-mode invalide : {!r}. Valeurs valides : {}" -#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 +#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "Autoriser {} ?" @@ -1381,28 +1543,40 @@ msgid "Waiting for browser authorization" msgstr "En attente de l'autorisation du navigateur" #: src/iac_code/services/providers/aliyun_oauth.py:300 -msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." -msgstr "1. Le navigateur peut afficher official-cli ; il s'agit de l'application OAuth CLI officielle d'Alibaba Cloud." +msgid "" +"1. The browser may show official-cli; this is the Alibaba Cloud official " +"CLI OAuth application." +msgstr "" +"1. Le navigateur peut afficher official-cli ; il s'agit de l'application " +"OAuth CLI officielle d'Alibaba Cloud." #: src/iac_code/services/providers/aliyun_oauth.py:302 -msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgid "" +"2. If assignment is required, assign the RAM user or RAM role that is " +"signed in. User groups are not supported." msgstr "" -"2. Si une assignation est requise, assignez l'utilisateur RAM ou le rôle RAM actuellement connecté. Les groupes " -"d'utilisateurs ne sont pas pris en charge." +"2. Si une assignation est requise, assignez l'utilisateur RAM ou le rôle " +"RAM actuellement connecté. Les groupes d'utilisateurs ne sont pas pris en" +" charge." #: src/iac_code/services/providers/aliyun_oauth.py:306 msgid "" -"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " -"out of Alibaba Cloud and sign in again." +"3. After assignment, close the old authorization page and run OAuth Login" +" (Browser) again. If it still fails, sign out of Alibaba Cloud and sign " +"in again." msgstr "" -"3. Après l'assignation, fermez l'ancienne page d'autorisation et relancez Connexion OAuth (navigateur). Si l'échec " -"persiste, déconnectez-vous d'Alibaba Cloud puis reconnectez-vous." +"3. Après l'assignation, fermez l'ancienne page d'autorisation et relancez" +" Connexion OAuth (navigateur). Si l'échec persiste, déconnectez-vous " +"d'Alibaba Cloud puis reconnectez-vous." #: src/iac_code/services/providers/aliyun_oauth.py:310 -msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgid "" +"4. STS credentials refresh when possible until Alibaba Cloud expires " +"them. If refresh fails, run /auth again." msgstr "" -"4. Les identifiants STS sont actualisés lorsque c'est possible jusqu'à leur expiration par Alibaba Cloud. Si " -"l'actualisation échoue, exécutez de nouveau /auth." +"4. Les identifiants STS sont actualisés lorsque c'est possible jusqu'à " +"leur expiration par Alibaba Cloud. Si l'actualisation échoue, exécutez de" +" nouveau /auth." #: src/iac_code/services/providers/aliyun_oauth.py:313 msgid "Press Esc to cancel while waiting." @@ -1410,16 +1584,21 @@ msgstr "Appuyez sur Échap pour annuler l'attente." #: src/iac_code/services/providers/aliyun_oauth.py:321 msgid "" -"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" -" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " -"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to " +"assign the official-cli application, assign it to the exact RAM user or " +"RAM role currently signed in. User groups are not supported. Then close " +"the old authorization page, sign out of Alibaba Cloud and sign in again " +"if needed, and run /auth to choose OAuth Login (Browser) again." msgstr "" -"Délai d'attente dépassé pour le callback OAuth. Si Alibaba Cloud vous a demandé d'assigner l'application official-" -"cli, assignez-la à l'utilisateur RAM ou au rôle RAM exact actuellement connecté. Les groupes d'utilisateurs ne sont " -"pas pris en charge. Fermez ensuite l'ancienne page d'autorisation, déconnectez-vous d'Alibaba Cloud et reconnectez-" -"vous si nécessaire, puis exécutez /auth pour choisir de nouveau Connexion OAuth (navigateur)." +"Délai d'attente dépassé pour le callback OAuth. Si Alibaba Cloud vous a " +"demandé d'assigner l'application official-cli, assignez-la à " +"l'utilisateur RAM ou au rôle RAM exact actuellement connecté. Les groupes" +" d'utilisateurs ne sont pas pris en charge. Fermez ensuite l'ancienne " +"page d'autorisation, déconnectez-vous d'Alibaba Cloud et reconnectez-vous" +" si nécessaire, puis exécutez /auth pour choisir de nouveau Connexion " +"OAuth (navigateur)." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "La compétence « {name} » est désactivée. Exécutez /skills pour l'activer." @@ -1439,8 +1618,12 @@ msgid "Skill disabled: {name}" msgstr "Compétence désactivée : {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." -msgstr "Examiner le code modifié pour la réutilisation, la qualité et l’efficacité, puis corriger les problèmes détectés." +msgid "" +"Review changed code for reuse, quality, and efficiency, then fix issues " +"found." +msgstr "" +"Examiner le code modifié pour la réutilisation, la qualité et " +"l’efficacité, puis corriger les problèmes détectés." #: src/iac_code/tools/edit_file.py:116 msgid "Edit" @@ -1538,8 +1721,9 @@ msgstr "L’URL ne peut pas être vide." #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Invalid URL: missing scheme (e.g. http:// or " -"https://). Got: {url}URL non valide : schéma manquant (p. ex. http:// ou https://). Obtenu : {url}" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}Invalid" +" URL: missing scheme (e.g. http:// or https://). Got: {url}URL non valide" +" : schéma manquant (p. ex. http:// ou https://). Obtenu : {url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1648,7 +1832,8 @@ msgstr "Règle(s) de refus correspondante(s) : {}" msgid "matched ask rule(s): {}" msgstr "Règle(s) de demande correspondante(s) : {}" -#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 +#: src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "Règle(s) d'autorisation correspondante(s) : {}" @@ -1705,7 +1890,8 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "Appel de {action}…" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 +#: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Appel réussi" @@ -1718,7 +1904,8 @@ msgstr "Réponse reçue ({count} lignes)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 +#: src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "Exécution de {action}…" @@ -1924,8 +2111,8 @@ msgstr "{count} documents trouvés (sur {total} au total)" #: src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py:143 msgid "Use web_fetch tool to read full document content if needed." msgstr "" -"Use web_fetch tool to read full document content if needed.Utilisez l’outil web_fetch pour lire le document en entier" -" si nécessaire." +"Use web_fetch tool to read full document content if needed.Utilisez " +"l’outil web_fetch pour lire le document en entier si nécessaire." #: src/iac_code/tools/cloud/aliyun/ros_stack.py:143 msgid "ROS Stack" @@ -1947,27 +2134,40 @@ msgid "" "Context:\n" "{context}" msgstr "" -"Erreur de syntaxe YAML dans le modèle (ligne {line}, colonne {col}) : {problem}\n" +"Erreur de syntaxe YAML dans le modèle (ligne {line}, colonne {col}) : " +"{problem}\n" "Contexte :\n" "{context}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:66 #, python-brace-format msgid "Template JSON syntax error (line {line}, column {col}): {msg}" -msgstr "Erreur de syntaxe JSON dans le modèle (ligne {line}, colonne {col}) : {msg}" +msgstr "" +"Erreur de syntaxe JSON dans le modèle (ligne {line}, colonne {col}) : " +"{msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "Template {fmt} parse result is not an object (dict), please check the template format" -msgstr "Le résultat de l'analyse {fmt} du modèle n'est pas un objet (dict), veuillez vérifier le format du modèle" +msgid "" +"Template {fmt} parse result is not an object (dict), please check the " +"template format" +msgstr "" +"Le résultat de l'analyse {fmt} du modèle n'est pas un objet (dict), " +"veuillez vérifier le format du modèle" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" -msgstr "Le modèle ne contient pas ROSTemplateFormatVersion (les modèles ROS doivent inclure ce champ, ex. '2015-09-01')" +msgid "" +"Template is missing ROSTemplateFormatVersion (ROS templates must include " +"this field, e.g. '2015-09-01')" +msgstr "" +"Le modèle ne contient pas ROSTemplateFormatVersion (les modèles ROS " +"doivent inclure ce champ, ex. '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" -msgstr "Le modèle ne contient pas Resources (les modèles ROS doivent inclure Resources)" +msgstr "" +"Le modèle ne contient pas Resources (les modèles ROS doivent inclure " +"Resources)" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:112 #, python-brace-format @@ -1976,8 +2176,12 @@ msgstr "Resources doit être un objet (dict), le type actuel est {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "Resource '{name}' definition must be an object (dict), current type is {type}" -msgstr "La définition de la ressource '{name}' doit être un objet (dict), le type actuel est {type}" +msgid "" +"Resource '{name}' definition must be an object (dict), current type is " +"{type}" +msgstr "" +"La définition de la ressource '{name}' doit être un objet (dict), le type" +" actuel est {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 #, python-brace-format @@ -1987,11 +2191,17 @@ msgstr "La ressource '{name}' n'a pas le champ Type" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:129 #, python-brace-format msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" -msgstr "La ressource '{name}' a le type incorrect '{wrong}', devrait être '{correct}'" +msgstr "" +"La ressource '{name}' a le type incorrect '{wrong}', devrait être " +"'{correct}'" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "Template structure validation found the following issues, please fix and retry:" -msgstr "La validation de la structure du modèle a détecté les problèmes suivants, veuillez corriger et réessayer :" +msgid "" +"Template structure validation found the following issues, please fix and " +"retry:" +msgstr "" +"La validation de la structure du modèle a détecté les problèmes suivants," +" veuillez corriger et réessayer :" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 #, python-brace-format @@ -2011,28 +2221,30 @@ msgstr "Notes de version" msgid "Run {} to update." msgstr "Exécutez {} pour mettre à jour." -#: src/iac_code/ui/banner.py:105 +#: src/iac_code/ui/banner.py:110 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "Votre assistant Infrastructure as Code assisté par IA" -#: src/iac_code/ui/banner.py:131 +#: src/iac_code/ui/banner.py:144 msgid "Welcome back" msgstr "Bon retour" -#: src/iac_code/ui/banner.py:148 +#: src/iac_code/ui/banner.py:161 msgid "Debug mode" msgstr "Mode debug" -#: src/iac_code/ui/banner.py:149 +#: src/iac_code/ui/banner.py:162 msgid "Log file" msgstr "Fichier journal" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 +#: src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "Réflexion pendant {seconds:.1f}s" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 +#: src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o pour développer)" @@ -2060,7 +2272,9 @@ msgstr "Terminé ({child_count} utilisations d’outil{token_info}{elapsed})" #: src/iac_code/ui/renderer.py:572 #, python-brace-format msgid "+ {count} more tool uses (ctrl+o to expand)" -msgstr "+ {count} more tool uses (ctrl+o to expand)+ {count} utilisations d’outil supplémentaires (ctrl+o pour développer)" +msgstr "" +"+ {count} more tool uses (ctrl+o to expand)+ {count} utilisations d’outil" +" supplémentaires (ctrl+o pour développer)" #: src/iac_code/ui/renderer.py:1177 #, python-brace-format @@ -2118,99 +2332,115 @@ msgstr "Non, toujours refuser \"{rule}\" (cette session)" msgid "No, always reject this tool" msgstr "Non, toujours refuser cet outil" -#: src/iac_code/ui/repl.py:406 +#: src/iac_code/ui/repl.py:412 msgid "Press Ctrl+C again to exit." msgstr "Appuyez de nouveau sur Ctrl+C pour quitter." -#: src/iac_code/ui/repl.py:431 +#: src/iac_code/ui/repl.py:437 msgid "Interrupted." msgstr "Interrompu." -#: src/iac_code/ui/repl.py:468 -msgid "Goodbye!" -msgstr "Au revoir !" - -#: src/iac_code/ui/repl.py:469 -msgid "Resume this session with:" -msgstr "Pour reprendre cette session :" - -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:496 msgid "Update now" msgstr "Mettre à jour maintenant" -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:498 msgid "Run the shown update command and exit when it succeeds." msgstr "Exécute la commande de mise à jour affichée et quitte en cas de succès." -#: src/iac_code/ui/repl.py:499 +#: src/iac_code/ui/repl.py:501 msgid "Skip" msgstr "Ignorer" -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:503 msgid "Continue with the current version for this session." msgstr "Continuer avec la version actuelle pour cette session." -#: src/iac_code/ui/repl.py:504 +#: src/iac_code/ui/repl.py:506 msgid "Skip until next version" msgstr "Ignorer jusqu’à la prochaine version" -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:508 msgid "Hide this update until a newer version is available." -msgstr "Masquer cette mise à jour jusqu’à ce qu’une version plus récente soit disponible." +msgstr "" +"Masquer cette mise à jour jusqu’à ce qu’une version plus récente soit " +"disponible." -#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 +#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 msgid "Update command failed. Continuing with the current version." msgstr "La commande de mise à jour a échoué. La version actuelle sera conservée." -#: src/iac_code/ui/repl.py:530 +#: src/iac_code/ui/repl.py:532 msgid "Update completed. Restart iac-code to continue." msgstr "Mise à jour terminée. Redémarrez iac-code pour continuer." -#: src/iac_code/ui/repl.py:568 +#: src/iac_code/ui/repl.py:570 msgid "No image in clipboard." msgstr "Aucune image dans le presse-papiers." -#: src/iac_code/ui/repl.py:754 +#: src/iac_code/ui/repl.py:756 msgid "Usage: !" msgstr "Utilisation : !" -#: src/iac_code/ui/repl.py:759 +#: src/iac_code/ui/repl.py:761 msgid "Shell command support is unavailable." msgstr "La prise en charge des commandes shell n'est pas disponible." -#: src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." -msgstr "Compétence inconnue : ${name}. Tapez / pour lister les commandes et les compétences." +msgstr "" +"Compétence inconnue : ${name}. Tapez / pour lister les commandes et les " +"compétences." -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:830 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" -"Unknown command: /{name}. Type /help for available commands.Commande inconnue : /{name}. Saisissez /help pour la " -"liste des commandes." +"Unknown command: /{name}. Type /help for available commands.Commande " +"inconnue : /{name}. Saisissez /help pour la liste des commandes." -#: src/iac_code/ui/repl.py:833 +#: src/iac_code/ui/repl.py:835 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ n'invoque que des compétences. Utilisez plutôt /{name}." -#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 +#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 #, python-brace-format msgid "Command error: {error}" msgstr "Erreur de commande : {error}" -#: src/iac_code/ui/repl.py:862 +#: src/iac_code/ui/repl.py:864 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Aucun gestionnaire pour la commande : {name}" -#: src/iac_code/ui/repl.py:1167 +#: src/iac_code/ui/repl.py:1126 +msgid "Goodbye!" +msgstr "Au revoir !" + +#: src/iac_code/ui/repl.py:1127 +msgid "Resume this session with:" +msgstr "Pour reprendre cette session :" + +#: src/iac_code/ui/repl.py:1130 +msgid "Session ID" +msgstr "ID de session" + +#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Session introuvable : {session_id}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1233 +msgid "Session name: " +msgstr "Nom de session : " + +#: src/iac_code/ui/repl.py:1239 +msgid "Session name cannot be empty." +msgstr "Le nom de session ne peut pas être vide." + +#: src/iac_code/ui/repl.py:1251 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2221,33 +2451,43 @@ msgstr "" "Pour la reprendre, exécutez :\n" " {cmd}" -#: src/iac_code/ui/repl.py:1287 +#: src/iac_code/ui/repl.py:1257 +msgid "Multiple sessions match. Resume one by ID:" +msgstr "Plusieurs sessions correspondent. Reprenez-en une par ID :" + +#: src/iac_code/ui/repl.py:1370 msgid "This conversation is from a different directory." msgstr "Cette conversation provient d’un autre répertoire." -#: src/iac_code/ui/repl.py:1289 +#: src/iac_code/ui/repl.py:1372 msgid "To resume, run:" msgstr "Pour reprendre, exécutez :" -#: src/iac_code/ui/repl.py:1294 +#: src/iac_code/ui/repl.py:1377 msgid "(Command copied to clipboard)" msgstr "(Commande copiée dans le presse-papiers)" -#: src/iac_code/ui/repl.py:1451 +#: src/iac_code/ui/repl.py:1534 #, python-brace-format -msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." +msgid "" +"Current model {model} does not support image input. Use /model to switch " +"to a vision-capable model." msgstr "" -"Le modèle actuel {model} ne prend pas en charge l’entrée d’image. Utilisez /model pour passer à un modèle compatible " -"vision." +"Le modèle actuel {model} ne prend pas en charge l’entrée d’image. " +"Utilisez /model pour passer à un modèle compatible vision." -#: src/iac_code/ui/repl.py:1460 +#: src/iac_code/ui/repl.py:1543 #, python-brace-format msgid "Image error: {err}" msgstr "Erreur d’image : {err}" -#: src/iac_code/ui/repl.py:1477 -msgid "Failed to persist image to cache; it will only exist in memory for this turn." -msgstr "Impossible de persister l’image dans le cache ; elle n’existera qu’en mémoire pour ce tour." +#: src/iac_code/ui/repl.py:1560 +msgid "" +"Failed to persist image to cache; it will only exist in memory for this " +"turn." +msgstr "" +"Impossible de persister l’image dans le cache ; elle n’existera qu’en " +"mémoire pour ce tour." #: src/iac_code/ui/spinner.py:52 msgid "Processing" @@ -2337,87 +2577,87 @@ msgstr "Saisissez pour rechercher des fichiers…" msgid "No matching files" msgstr "Aucun fichier correspondant" -#: src/iac_code/ui/dialogs/resume_picker.py:114 +#: src/iac_code/ui/dialogs/resume_picker.py:116 msgid "Search..." msgstr "Rechercher…" -#: src/iac_code/ui/dialogs/resume_picker.py:359 +#: src/iac_code/ui/dialogs/resume_picker.py:374 msgid "Resume Session" msgstr "Reprendre la session" -#: src/iac_code/ui/dialogs/resume_picker.py:369 +#: src/iac_code/ui/dialogs/resume_picker.py:384 msgid "No sessions found" msgstr "Aucune session trouvée" -#: src/iac_code/ui/dialogs/resume_picker.py:427 +#: src/iac_code/ui/dialogs/resume_picker.py:444 msgid "show current dir" msgstr "afficher le répertoire actuel" -#: src/iac_code/ui/dialogs/resume_picker.py:429 +#: src/iac_code/ui/dialogs/resume_picker.py:446 msgid "show all projects" msgstr "afficher tous les projets" -#: src/iac_code/ui/dialogs/resume_picker.py:432 +#: src/iac_code/ui/dialogs/resume_picker.py:449 msgid "show all branches" msgstr "afficher toutes les branches" -#: src/iac_code/ui/dialogs/resume_picker.py:434 +#: src/iac_code/ui/dialogs/resume_picker.py:451 msgid "only show current branch" msgstr "afficher uniquement la branche actuelle" -#: src/iac_code/ui/dialogs/resume_picker.py:435 +#: src/iac_code/ui/dialogs/resume_picker.py:452 msgid "preview" msgstr "aperçu" -#: src/iac_code/ui/dialogs/resume_picker.py:436 +#: src/iac_code/ui/dialogs/resume_picker.py:453 msgid "Type to search" msgstr "Saisir pour rechercher" -#: src/iac_code/ui/dialogs/resume_picker.py:437 +#: src/iac_code/ui/dialogs/resume_picker.py:454 msgid "cancel" msgstr "annuler" -#: src/iac_code/ui/dialogs/resume_picker.py:552 +#: src/iac_code/ui/dialogs/resume_picker.py:569 #, python-brace-format msgid "{n} more line{s}" msgstr "{n} ligne{s} supplémentaire{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:566 +#: src/iac_code/ui/dialogs/resume_picker.py:583 #, python-brace-format msgid "{n} message{s}" msgstr "{n} message{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:580 +#: src/iac_code/ui/dialogs/resume_picker.py:597 msgid "resume" msgstr "reprendre" -#: src/iac_code/ui/dialogs/resume_picker.py:584 +#: src/iac_code/ui/dialogs/resume_picker.py:601 msgid "back" msgstr "retour" -#: src/iac_code/ui/dialogs/resume_picker.py:589 +#: src/iac_code/ui/dialogs/resume_picker.py:606 msgid "scroll" msgstr "défiler" -#: src/iac_code/ui/dialogs/resume_picker.py:608 +#: src/iac_code/ui/dialogs/resume_picker.py:625 msgid "(empty session)" msgstr "(session vide)" -#: src/iac_code/ui/dialogs/resume_picker.py:728 +#: src/iac_code/ui/dialogs/resume_picker.py:745 msgid "just now" msgstr "à l’instant" -#: src/iac_code/ui/dialogs/resume_picker.py:731 +#: src/iac_code/ui/dialogs/resume_picker.py:748 #, python-brace-format msgid "{n} minute{s} ago" msgstr "il y a {n} minute{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:734 +#: src/iac_code/ui/dialogs/resume_picker.py:751 #, python-brace-format msgid "{n} hour{s} ago" msgstr "il y a {n} heure{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:736 +#: src/iac_code/ui/dialogs/resume_picker.py:753 #, python-brace-format msgid "{n} day{s} ago" msgstr "il y a {n} jour{s}" @@ -2437,8 +2677,12 @@ msgstr "{current} sur {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" -msgstr "{count} compétences - Espace pour activer/désactiver, Entrée pour enregistrer, Tab pour trier, Échap pour annuler" +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "" +"{count} compétences - Espace pour activer/désactiver, Entrée pour " +"enregistrer, Tab pour trier, Échap pour annuler" #: src/iac_code/ui/dialogs/skills_picker.py:171 #, python-brace-format @@ -2524,8 +2768,12 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code sous Windows nécessite Git for Windows." #: src/iac_code/utils/platform.py:41 -msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." -msgstr "S'il est installé mais absent du PATH, définissez la variable d'environnement IAC_CODE_GIT_BASH_PATH." +msgid "" +"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " +"variable." +msgstr "" +"S'il est installé mais absent du PATH, définissez la variable " +"d'environnement IAC_CODE_GIT_BASH_PATH." #: src/iac_code/utils/platform.py:44 msgid "To install:" @@ -2536,8 +2784,12 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " Option 1 - winget (nécessite l'accès à github.com) :" #: src/iac_code/utils/platform.py:52 -msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" -msgstr " Option 2 - si vous ne pouvez pas accéder à github.com, exécutez ceci pour installer via npmmirror :" +msgid "" +" Option 2 - if you cannot reach github.com, run this to install via " +"npmmirror:" +msgstr "" +" Option 2 - si vous ne pouvez pas accéder à github.com, exécutez ceci " +"pour installer via npmmirror :" #~ msgid "DashScope Token Plan" #~ msgstr "DashScope Token Plan" @@ -2545,8 +2797,14 @@ msgstr " Option 2 - si vous ne pouvez pas accéder à github.com, exécutez cec #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." -#~ msgstr "Le fournisseur LLM est verrouillé par '{source}'. Pour changer, modifiez llm_source dans settings.yml." +#~ msgid "" +#~ "LLM provider is locked by '{source}'." +#~ " To change, modify llm_source in " +#~ "settings.yml." +#~ msgstr "" +#~ "Le fournisseur LLM est verrouillé par" +#~ " '{source}'. Pour changer, modifiez " +#~ "llm_source dans settings.yml." #~ msgid "Provider switched: {status}" #~ msgstr "Provider changé : {status}" @@ -2563,9 +2821,16 @@ msgstr " Option 2 - si vous ne pouvez pas accéder à github.com, exécutez cec #~ msgid "Cache create" #~ msgstr "Création du cache" -#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" #~ msgstr "" -#~ "{count} compétences - Espace pour activer/désactiver, Entrée pour " -#~ "enregistrer, / pour rechercher, t pour trier, Échap pour " -#~ "annuler" +#~ "{count} compétences - Espace pour " +#~ "activer/désactiver, Entrée pour enregistrer, /" +#~ " pour rechercher, t pour trier, Échap" +#~ " pour annuler" + +#~ msgid "Resume a session by ID" +#~ msgstr "Reprendre une session par identifiant" 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 fe25d6b..e0ab65b 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 21:26+0800\n" +"POT-Creation-Date: 2026-06-02 22:08+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: ja\n" @@ -23,79 +23,124 @@ msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): { msgstr "無効な IAC_CODE_PROVIDER 値: {!r}。有効な値 (大文字と小文字を区別しません): {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." -msgstr "Unix ドメインソケットトランスポートは Windows ではサポートされていません。--transport http または --transport stdio を使用してください。" +msgid "" +"Unix domain socket transport is not supported on Windows. Use --transport" +" http or --transport stdio instead." +msgstr "" +"Unix ドメインソケットトランスポートは Windows ではサポートされていません。--transport http または " +"--transport stdio を使用してください。" + +#: src/iac_code/acp/server.py:413 src/iac_code/acp/server.py:429 +#: src/iac_code/acp/server.py:456 +msgid "Session not found" +msgstr "セッションが見つかりません" + +#: src/iac_code/acp/server.py:416 +#, python-brace-format +msgid "Session name is ambiguous. Candidates: {candidates}" +msgstr "セッション名があいまいです。候補: {candidates}" -#: src/iac_code/acp/slash_registry.py:44 +#: src/iac_code/acp/server.py:434 src/iac_code/acp/server.py:708 #, python-brace-format -msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" +msgid "Session belongs to another project. Run: {hint}" +msgstr "セッションは別のプロジェクトに属しています。実行: {hint}" + +#: src/iac_code/acp/slash_registry.py:46 +#, python-brace-format +msgid "" +"Command '/{cmd_name}' is not supported over ACP. Supported commands: " +"{supported}" msgstr "コマンド '/{cmd_name}' は ACP 上ではサポートされていません。サポートされているコマンド:{supported}" -#: src/iac_code/acp/slash_registry.py:58 +#: src/iac_code/acp/slash_registry.py:62 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "コマンド '/{cmd_name}' のハンドラーは未実装です。" -#: src/iac_code/acp/slash_registry.py:70 +#: src/iac_code/acp/slash_registry.py:74 #, python-brace-format msgid "Compaction failed: {error}" msgstr "圧縮に失敗しました:{error}" -#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:77 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "圧縮できる内容がありません:会話が空です。" -#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." +msgid "" +"Conversation too short to compact: all messages are within the recent " +"{turns}-turn preservation window." msgstr "会話が短すぎて圧縮できません:すべてのメッセージが直近 {turns} ターンの保持ウィンドウ内にあります。" -#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:84 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "圧縮に失敗しました。詳細はログをご確認ください。" -#: src/iac_code/acp/slash_registry.py:85 +#: src/iac_code/acp/slash_registry.py:89 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" -msgstr "コンテキストを圧縮しました:{original} → {compacted} tokens({percent} 削減)。 コンテキスト使用量:{usage}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent} reduction)." +" Context usage: {usage}" +msgstr "" +"コンテキストを圧縮しました:{original} → {compacted} tokens({percent} 削減)。 " +"コンテキスト使用量:{usage}" -#: src/iac_code/acp/slash_registry.py:99 +#: src/iac_code/acp/slash_registry.py:103 #, python-brace-format msgid "Clear failed: {error}" msgstr "クリアに失敗しました:{error}" -#: src/iac_code/acp/slash_registry.py:100 +#: src/iac_code/acp/slash_registry.py:104 msgid "Conversation history cleared." msgstr "会話履歴をクリアしました。" -#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:120 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "デバッグログはオンです。ログファイル:{path}" -#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "デバッグログはオフです。" -#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "デバッグログを有効にしました。ログファイル:{path}" -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:129 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "デバッグログを無効にしました。" -#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:131 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "使用方法:/debug [on|off]" -#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +#: src/iac_code/acp/slash_registry.py:136 src/iac_code/commands/memory.py:84 msgid "Memory manager is unavailable." msgstr "メモリマネージャーを利用できません。" -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 -#: src/iac_code/ui/repl.py:807 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +msgid "Usage: /rename " +msgstr "使用法: /rename <名前>" + +#: src/iac_code/acp/slash_registry.py:152 +msgid "Rename is only available after a session is created." +msgstr "セッション作成後にのみ名前を変更できます。" + +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#, python-brace-format +msgid "Session is already named {name}" +msgstr "セッション名はすでに {name} です" + +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#, python-brace-format +msgid "Renamed session to {name}" +msgstr "セッション名を {name} に変更しました" + +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 msgid "Permission denied." msgstr "権限が拒否されました。" @@ -112,7 +157,8 @@ msgstr "探索" msgid "Plan" msgstr "計画" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 +#: src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "エージェント" @@ -164,14 +210,16 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-" +"code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " 修正方法:iac-code を実行し、/auth と入力してください\n" " または環境変数を設定:IAC_CODE_API_KEY=<あなたのキー>\n" -" ドキュメント:https://aliyun.github.io/iac-code/ja/docs/configuration/authentication\n" +" ドキュメント:https://aliyun.github.io/iac-" +"code/ja/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -193,9 +241,11 @@ msgstr "インストールに失敗しました(PowerShell 終了コード {} #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " -"non-standard path." -msgstr "インストーラーは終了しましたが、一般的な場所に bash.exe が見つかりませんでした。UAC がキャンセルされたか、インストーラーが標準外のパスを使用した可能性があります。" +"Installer exited but bash.exe was not found in common locations; UAC may " +"have been cancelled or the installer used a non-standard path." +msgstr "" +"インストーラーは終了しましたが、一般的な場所に bash.exe が見つかりませんでした。UAC " +"がキャンセルされたか、インストーラーが標準外のパスを使用した可能性があります。" #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -218,8 +268,11 @@ msgstr "npmmirror ミラー経由で Git for Windows をインストールしま msgid "YAML config file containing A2A client options" msgstr "A2A クライアントオプションを含む YAML 設定ファイル" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 +#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "" +"A2A client dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" msgstr "A2A クライアントの依存関係が不足しています。次のコマンドでインストールしてください: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 @@ -238,7 +291,8 @@ msgstr "出力形式:text、json、stream-json" msgid "Maximum agent turns in headless mode" msgstr "ヘッドレスモードでの最大エージェンターン数" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 +#: src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "デバッグログを有効にする" @@ -251,8 +305,8 @@ msgid "Show version and exit" msgstr "バージョンを表示して終了する" #: src/iac_code/cli/main.py:89 -msgid "Resume a session by ID" -msgstr "ID でセッションを再開する" +msgid "Resume a session by ID or name" +msgstr "ID または名前でセッションを再開" #: src/iac_code/cli/main.py:90 msgid "Resume the most recent session" @@ -263,11 +317,15 @@ msgid "Install completion for the current shell." msgstr "現在の shell に補完をインストールします。" #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "Show completion for the current shell, to copy it or customize the installation." +msgid "" +"Show completion for the current shell, to copy it or customize the " +"installation." msgstr "現在の shell 向けの補完スクリプトを表示します。コピーしたり、インストールをカスタマイズしたりできます。" #: src/iac_code/cli/main.py:110 -msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" +msgid "" +"Comma-separated tool permission patterns to allow, e.g. 'bash(git " +"*),write_file'" msgstr "許可するツール権限パターン(カンマ区切り)、例: 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 @@ -312,27 +370,39 @@ msgid "YAML config file for A2A server options" msgstr "A2A サーバーオプション用の YAML 設定ファイル" #: src/iac_code/cli/main.py:584 -msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." -msgstr "HTTP サーバーポート。41242 は Gemini CLI に触発された iac-code のデフォルトで、登録済みの A2A ポートではありません。" +msgid "" +"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " +"not a registered A2A port." +msgstr "" +"HTTP サーバーポート。41242 は Gemini CLI に触発された iac-code のデフォルトで、登録済みの A2A " +"ポートではありません。" #: src/iac_code/cli/main.py:589 -msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" +msgid "" +"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " +"redis-streams" msgstr "A2A トランスポート: http、stdio、unix、websocket、grpc、grpc-jsonrpc、または redis-streams" #: src/iac_code/cli/main.py:595 -msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." +msgid "" +"Expose A2A thinking signal types; repeat for multiple. Values: raw-" +"thinking, tool-trace." msgstr "A2A thinking 信号タイプを公開します。複数指定するには繰り返します。値:raw-thinking、tool-trace。" #: src/iac_code/cli/main.py:646 -msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgid "" +"A2A server dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" msgstr "A2A サーバーの依存関係が不足しています。次のコマンドでインストールしてください: pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "A2A JSON-RPC エンドポイントにプロンプトを送信します。" -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 +#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 +#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "A2A JSON-RPC エンドポイント URL" @@ -357,33 +427,48 @@ msgstr "リクエストと共に送信する作業ディレクトリのメタデ msgid "A2A context ID to continue" msgstr "継続する A2A コンテキスト ID" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 +#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 +#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 +#: src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "A2A HTTP リクエスト用の Bearer トークン" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 +#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 +#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 +#: src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "A2A HTTP リクエスト用の Basic 認証ユーザー名" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 +#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 +#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 +#: src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "A2A HTTP リクエスト用の Basic 認証パスワード" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 +#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 +#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 +#: src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "A2A HTTP リクエスト用の API キー" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 +#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 +#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 +#: src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "A2A API キー用の HTTP ヘッダー名" @@ -419,8 +504,10 @@ msgstr "A2A エージェントのベース URL" msgid "Get an A2A task." msgstr "A2A タスクを取得します。" -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 +#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 +#: src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "A2A タスク ID" @@ -468,7 +555,8 @@ msgstr "A2A タスクのイベントストリームを購読します。" msgid "Create an A2A task push notification config." msgstr "A2A タスクプッシュ通知設定を作成します。" -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 +#: src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "プッシュ設定 ID" @@ -532,81 +620,89 @@ msgstr "永続化された A2A ルート用のディレクトリ" msgid "Save the provided routes as a route snapshot" msgstr "指定されたルートをルートスナップショットとして保存します" -#: src/iac_code/commands/__init__.py:25 +#: src/iac_code/commands/__init__.py:26 msgid "Show available commands" msgstr "利用可能なコマンドを表示します" -#: src/iac_code/commands/__init__.py:34 +#: src/iac_code/commands/__init__.py:35 msgid "Clear conversation history" msgstr "会話履歴をクリアします" -#: src/iac_code/commands/__init__.py:42 +#: src/iac_code/commands/__init__.py:43 msgid "Show or switch model" msgstr "モデルを表示または切り替えます" -#: src/iac_code/commands/__init__.py:51 +#: src/iac_code/commands/__init__.py:52 msgid "Show or switch thinking effort" msgstr "思考の負荷(effort)を表示または切り替えます" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compact conversation context" msgstr "会話コンテキストを圧縮します" -#: src/iac_code/commands/__init__.py:62 +#: src/iac_code/commands/__init__.py:63 msgid "Compacting conversation" msgstr "会話を圧縮しています" -#: src/iac_code/commands/__init__.py:69 +#: src/iac_code/commands/__init__.py:70 msgid "Exit the application" msgstr "アプリケーションを終了します" -#: src/iac_code/commands/__init__.py:78 +#: src/iac_code/commands/__init__.py:79 msgid "Authenticate with LLM provider" msgstr "LLM プロバイダーで認証を行います" -#: src/iac_code/commands/__init__.py:87 +#: src/iac_code/commands/__init__.py:88 msgid "Toggle debug logging" msgstr "デバッグログのオン/オフを切り替えます" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "View and manage persistent memories" msgstr "永続メモリを表示および管理" -#: src/iac_code/commands/__init__.py:98 +#: src/iac_code/commands/__init__.py:99 msgid "[|search |delete |help]" msgstr "[<名前>|search <検索語>|delete <名前>|help]" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "Resume a previous session" msgstr "以前のセッションを再開します" -#: src/iac_code/commands/__init__.py:107 +#: src/iac_code/commands/__init__.py:108 msgid "[conversation id or search term]" msgstr "[会話 ID または検索語]" -#: src/iac_code/commands/__init__.py:114 +#: src/iac_code/commands/__init__.py:115 +msgid "Rename the current session" +msgstr "現在のセッション名を変更" + +#: src/iac_code/commands/__init__.py:124 msgid "Manage skills" msgstr "スキルを管理" -#: src/iac_code/commands/__init__.py:122 +#: src/iac_code/commands/__init__.py:132 msgid "Show current session status" msgstr "現在のセッション状態を表示" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "移動" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 -#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "確認" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 -#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 -#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 +#: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 +#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "戻る" @@ -618,8 +714,9 @@ msgstr "維持" msgid "Re-enter" msgstr "再入力" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 +#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 +#: src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (現在)" @@ -656,17 +753,20 @@ msgstr "IaC クラウドサービスを設定" msgid "Select configuration type" msgstr "設定の種類を選択" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 +#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 +#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "認証をキャンセルしました" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 +#: src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "プロバイダーを選択 — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 +#: src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "サードパーティ" @@ -762,8 +862,9 @@ msgstr "クラウドプロバイダーを選択" msgid "Credential" msgstr "クレデンシャル" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 -#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 +#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "リージョン" @@ -874,7 +975,9 @@ msgstr "クレデンシャルの種類を選択" #: src/iac_code/commands/auth.py:1510 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" -msgstr "Configured: Alibaba Cloud credentials saved to ~/.iac-code設定しました:Alibaba Cloud のクレデンシャルを ~/.iac-code に保存しました" +msgstr "" +"Configured: Alibaba Cloud credentials saved to ~/.iac-code設定しました:Alibaba " +"Cloud のクレデンシャルを ~/.iac-code に保存しました" #: src/iac_code/commands/auth.py:1517 msgid "Configure Alibaba Cloud region" @@ -898,8 +1001,12 @@ msgstr "アクティブなエージェントループがありません。" #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" -msgstr "コンテキストを圧縮しました:{original} → {compacted} tokens({percent_display} 削減)。 コンテキスト使用量:{usage_display}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent_display} " +"reduction). Context usage: {usage_display}" +msgstr "" +"コンテキストを圧縮しました:{original} → {compacted} tokens({percent_display} 削減)。 " +"コンテキスト使用量:{usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -909,7 +1016,8 @@ msgstr "debug コマンドにはコンテキストが必要です。" msgid "No active session." msgstr "アクティブなセッションがありません。" -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 +#: src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "設定済みのプロバイダーがありません。先に /auth を実行してください。" @@ -1003,8 +1111,12 @@ msgstr "メモリ '{name}' を削除しました。" #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." -msgstr "モデルは '{source}' によって管理されています。モデルを変更するには {source} で調整するか、/auth で別のプロバイダーに切り替えてください。" +msgid "" +"Model is managed by '{source}'. To change model, modify it in {source} or" +" switch provider via /auth." +msgstr "" +"モデルは '{source}' によって管理されています。モデルを変更するには {source} で調整するか、/auth " +"で別のプロバイダーに切り替えてください。" #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1021,23 +1133,36 @@ msgstr "現在のモデル:{model}" msgid "Kept model as {model}" msgstr "モデルを {model} のままにしました" -#: src/iac_code/commands/resume.py:21 +#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +msgid "Rename is only available in interactive mode." +msgstr "名前の変更は対話モードでのみ使用できます。" + +#: src/iac_code/commands/rename.py:30 +msgid "Rename cancelled" +msgstr "名前の変更をキャンセルしました" + +#: src/iac_code/commands/resume.py:22 msgid "Resume is only available in interactive mode." msgstr "resume は対話モードでのみ利用できます。" -#: src/iac_code/commands/resume.py:27 +#: src/iac_code/commands/resume.py:28 msgid "Resume is unavailable: session index not initialised." msgstr "resume を使用できません:セッション索引が初期化されていません。" -#: src/iac_code/commands/resume.py:32 +#: src/iac_code/commands/resume.py:33 src/iac_code/commands/resume.py:36 #, python-brace-format msgid "Session not found: {arg}" msgstr "セッションが見つかりません:{arg}" -#: src/iac_code/commands/resume.py:47 +#: src/iac_code/commands/resume.py:52 src/iac_code/commands/resume.py:68 msgid "Resume cancelled" msgstr "resume をキャンセルしました" +#: src/iac_code/commands/resume.py:55 +#, python-brace-format +msgid "Unable to resolve session: {arg}" +msgstr "セッションを解決できません: {arg}" + #: src/iac_code/commands/skills.py:14 msgid "Skills management is only available in interactive mode." msgstr "スキル管理は対話モードでのみ使用できます。" @@ -1062,7 +1187,8 @@ msgstr "status コマンドには REPL コンテキストが必要です。" msgid "Status is only available in interactive mode." msgstr "status は対話モードでのみ利用できます。" -#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:136 +#: src/iac_code/ui/banner.py:138 msgid "Session" msgstr "セッション" @@ -1070,7 +1196,8 @@ msgstr "セッション" msgid "Provider" msgstr "プロバイダー" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "未設定" @@ -1170,7 +1297,9 @@ msgstr "中断しました。" #: src/iac_code/providers/manager.py:78 #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." -msgstr "Cannot determine provider for model: {model}. Run /auth to configure.モデル {model} のプロバイダーを特定できません。/auth を実行して設定してください。" +msgstr "" +"Cannot determine provider for model: {model}. Run /auth to configure.モデル " +"{model} のプロバイダーを特定できません。/auth を実行して設定してください。" #: src/iac_code/providers/manager.py:95 #, python-brace-format @@ -1179,22 +1308,30 @@ msgstr "不明なプロバイダーキー:'{key}'。/auth を実行して設 #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." +msgid "" +"No API key configured for provider '{provider}' (model: {model}). Run " +"/auth to configure." msgstr "プロバイダー '{provider}' の API キーが設定されていません(モデル: {model})。/auth を実行して設定してください。" #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " -"endpoints require a /v1 suffix (e.g. {base_url}/v1)." -msgstr "API からデータが返りませんでした。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI 互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" +"API returned no data. Please check that your API Base URL is correct " +"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " +"suffix (e.g. {base_url}/v1)." +msgstr "" +"API からデータが返りませんでした。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI " +"互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" -"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." -msgstr "API から無効な応答が返りました。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI 互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" +"API returned an invalid response. Please check that your API Base URL is " +"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" +" /v1 suffix (e.g. {base_url}/v1)." +msgstr "" +"API から無効な応答が返りました。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI " +"互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1275,21 +1412,35 @@ msgstr "Anthropic 互換" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " +"support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " -"settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " +"mode (remove 'llm_source: qwenpaw' from settings.yml)." msgstr "" -"[QwenPaw モード] プロバイダー '{provider_id}' を認識できません。iac-code はこのプロバイダーをサポートしていません。\n" +"[QwenPaw モード] プロバイダー '{provider_id}' を認識できません。iac-code " +"はこのプロバイダーをサポートしていません。\n" "サポートされている QwenPaw プロバイダー ID:{supported_ids}\n" -"対処法:QwenPaw でサポートされているプロバイダーに切り替えるか、QwenPaw モードを無効にしてください(settings.yml から 'llm_source: qwenpaw' を削除)。" +"対処法:QwenPaw でサポートされているプロバイダーに切り替えるか、QwenPaw モードを無効にしてください(settings.yml から" +" 'llm_source: qwenpaw' を削除)。" + +#: src/iac_code/services/session_metadata.py:52 +#, python-brace-format +msgid "Session name must match {pattern}" +msgstr "セッション名は {pattern} に一致する必要があります" + +#: src/iac_code/services/session_storage.py:241 +#, python-brace-format +msgid "Session name already exists in this project: {name}" +msgstr "このプロジェクトにはセッション名がすでに存在します: {name}" #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "無効な --permission-mode {!r} です。有効な値: {}" -#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 +#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "{} を許可しますか?" @@ -1357,22 +1508,35 @@ msgid "Waiting for browser authorization" msgstr "ブラウザー認可を待機しています" #: src/iac_code/services/providers/aliyun_oauth.py:300 -msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." -msgstr "1. ブラウザーに official-cli と表示される場合があります。これは Alibaba Cloud 公式 CLI OAuth アプリケーションです。" +msgid "" +"1. The browser may show official-cli; this is the Alibaba Cloud official " +"CLI OAuth application." +msgstr "" +"1. ブラウザーに official-cli と表示される場合があります。これは Alibaba Cloud 公式 CLI OAuth " +"アプリケーションです。" #: src/iac_code/services/providers/aliyun_oauth.py:302 -msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgid "" +"2. If assignment is required, assign the RAM user or RAM role that is " +"signed in. User groups are not supported." msgstr "2. 割り当てが必要な場合は、サインイン中の RAM ユーザーまたは RAM ロールを割り当ててください。ユーザーグループはサポートされていません。" #: src/iac_code/services/providers/aliyun_oauth.py:306 msgid "" -"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " -"out of Alibaba Cloud and sign in again." -msgstr "3. 割り当て後、古い認可ページを閉じて OAuth ログイン(ブラウザー)を再実行してください。それでも失敗する場合は、Alibaba Cloud からサインアウトして再度サインインしてください。" +"3. After assignment, close the old authorization page and run OAuth Login" +" (Browser) again. If it still fails, sign out of Alibaba Cloud and sign " +"in again." +msgstr "" +"3. 割り当て後、古い認可ページを閉じて OAuth ログイン(ブラウザー)を再実行してください。それでも失敗する場合は、Alibaba " +"Cloud からサインアウトして再度サインインしてください。" #: src/iac_code/services/providers/aliyun_oauth.py:310 -msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." -msgstr "4. STS 認証情報は Alibaba Cloud が期限切れにするまで可能な場合に更新されます。更新に失敗した場合は、/auth を再実行してください。" +msgid "" +"4. STS credentials refresh when possible until Alibaba Cloud expires " +"them. If refresh fails, run /auth again." +msgstr "" +"4. STS 認証情報は Alibaba Cloud が期限切れにするまで可能な場合に更新されます。更新に失敗した場合は、/auth " +"を再実行してください。" #: src/iac_code/services/providers/aliyun_oauth.py:313 msgid "Press Esc to cancel while waiting." @@ -1380,15 +1544,18 @@ msgstr "Esc を押すと待機をキャンセルできます。" #: src/iac_code/services/providers/aliyun_oauth.py:321 msgid "" -"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" -" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " -"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to " +"assign the official-cli application, assign it to the exact RAM user or " +"RAM role currently signed in. User groups are not supported. Then close " +"the old authorization page, sign out of Alibaba Cloud and sign in again " +"if needed, and run /auth to choose OAuth Login (Browser) again." msgstr "" -"OAuth コールバックの待機がタイムアウトしました。Alibaba Cloud から official-cli アプリケーションの割り当てを求められた場合は、現在サインインしている正確な RAM ユーザーまたは RAM " -"ロールに割り当ててください。ユーザーグループはサポートされていません。その後、古い認可ページを閉じ、必要に応じて Alibaba Cloud からサインアウトしてサインインし直し、/auth を実行して OAuth " -"ログイン(ブラウザー)を再度選択してください。" +"OAuth コールバックの待機がタイムアウトしました。Alibaba Cloud から official-cli " +"アプリケーションの割り当てを求められた場合は、現在サインインしている正確な RAM ユーザーまたは RAM " +"ロールに割り当ててください。ユーザーグループはサポートされていません。その後、古い認可ページを閉じ、必要に応じて Alibaba Cloud " +"からサインアウトしてサインインし直し、/auth を実行して OAuth ログイン(ブラウザー)を再度選択してください。" -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "スキル「{name}」は無効です。有効にするには /skills を実行してください。" @@ -1408,7 +1575,9 @@ msgid "Skill disabled: {name}" msgstr "スキルが無効です: {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." +msgid "" +"Review changed code for reuse, quality, and efficiency, then fix issues " +"found." msgstr "変更されたコードの再利用性、品質、効率を確認し、見つかった問題を修正してください。" #: src/iac_code/tools/edit_file.py:116 @@ -1506,7 +1675,9 @@ msgstr "URL を空にすることはできません。" #: src/iac_code/tools/web_fetch.py:100 #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" -msgstr "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}無効な URL:スキームがありません(例:http:// または https://)。値:{url}" +msgstr "" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}無効な " +"URL:スキームがありません(例:http:// または https://)。値:{url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1615,7 +1786,8 @@ msgstr "一致した拒否ルール: {}" msgid "matched ask rule(s): {}" msgstr "一致した確認ルール: {}" -#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 +#: src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "一致した許可ルール: {}" @@ -1672,7 +1844,8 @@ msgstr "CloudAPI" msgid "Calling {action}..." msgstr "{action} を呼び出しています…" -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 +#: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "呼び出しに成功しました" @@ -1685,7 +1858,8 @@ msgstr "応答を受信しました({count} 行)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 +#: src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "{action} を実行中…" @@ -1923,12 +2097,18 @@ msgstr "テンプレートの JSON 構文エラー({line} 行目、{col} 列 #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "Template {fmt} parse result is not an object (dict), please check the template format" +msgid "" +"Template {fmt} parse result is not an object (dict), please check the " +"template format" msgstr "テンプレートの {fmt} 解析結果がオブジェクト(dict)ではありません。テンプレートの形式を確認してください" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" -msgstr "テンプレートに ROSTemplateFormatVersion がありません(ROS テンプレートにはこのフィールドが必須です。例: '2015-09-01')" +msgid "" +"Template is missing ROSTemplateFormatVersion (ROS templates must include " +"this field, e.g. '2015-09-01')" +msgstr "" +"テンプレートに ROSTemplateFormatVersion がありません(ROS テンプレートにはこのフィールドが必須です。例: " +"'2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" @@ -1941,7 +2121,9 @@ msgstr "Resources はオブジェクト(dict)でなければなりません #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "Resource '{name}' definition must be an object (dict), current type is {type}" +msgid "" +"Resource '{name}' definition must be an object (dict), current type is " +"{type}" msgstr "リソース '{name}' の定義はオブジェクト(dict)でなければなりません。現在の型は {type} です" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 @@ -1955,7 +2137,9 @@ msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" msgstr "リソース '{name}' の型 '{wrong}' が正しくありません。'{correct}' に変更してください" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "Template structure validation found the following issues, please fix and retry:" +msgid "" +"Template structure validation found the following issues, please fix and " +"retry:" msgstr "テンプレート構造の検証で以下の問題が見つかりました。修正して再試行してください:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 @@ -1976,28 +2160,30 @@ msgstr "リリースノート" msgid "Run {} to update." msgstr "更新するには {} を実行してください。" -#: src/iac_code/ui/banner.py:105 +#: src/iac_code/ui/banner.py:110 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "あなたの AI 駆動の Infrastructure as Code アシスタントです" -#: src/iac_code/ui/banner.py:131 +#: src/iac_code/ui/banner.py:144 msgid "Welcome back" msgstr "おかえりなさい" -#: src/iac_code/ui/banner.py:148 +#: src/iac_code/ui/banner.py:161 msgid "Debug mode" msgstr "デバッグモード" -#: src/iac_code/ui/banner.py:149 +#: src/iac_code/ui/banner.py:162 msgid "Log file" msgstr "ログファイル" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 +#: src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "{seconds:.1f} 秒考えました" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 +#: src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o で展開)" @@ -2083,97 +2269,111 @@ msgstr "いいえ、常に \"{rule}\" を拒否(このセッション)" msgid "No, always reject this tool" msgstr "いいえ、このツールは常に拒否" -#: src/iac_code/ui/repl.py:406 +#: src/iac_code/ui/repl.py:412 msgid "Press Ctrl+C again to exit." msgstr "終了するには Ctrl+C をもう一度押してください。" -#: src/iac_code/ui/repl.py:431 +#: src/iac_code/ui/repl.py:437 msgid "Interrupted." msgstr "中断しました。" -#: src/iac_code/ui/repl.py:468 -msgid "Goodbye!" -msgstr "さようなら。" - -#: src/iac_code/ui/repl.py:469 -msgid "Resume this session with:" -msgstr "このセッションを再開するには次を実行してください:" - -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:496 msgid "Update now" msgstr "今すぐ更新" -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:498 msgid "Run the shown update command and exit when it succeeds." msgstr "表示された更新コマンドを実行し、成功したら終了します。" -#: src/iac_code/ui/repl.py:499 +#: src/iac_code/ui/repl.py:501 msgid "Skip" msgstr "スキップ" -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:503 msgid "Continue with the current version for this session." msgstr "このセッションでは現在のバージョンを使い続けます。" -#: src/iac_code/ui/repl.py:504 +#: src/iac_code/ui/repl.py:506 msgid "Skip until next version" msgstr "次のバージョンまでスキップ" -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:508 msgid "Hide this update until a newer version is available." msgstr "より新しいバージョンが利用可能になるまで、この更新を非表示にします。" -#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 +#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 msgid "Update command failed. Continuing with the current version." msgstr "更新コマンドに失敗しました。現在のバージョンで続行します。" -#: src/iac_code/ui/repl.py:530 +#: src/iac_code/ui/repl.py:532 msgid "Update completed. Restart iac-code to continue." msgstr "更新が完了しました。続行するには iac-code を再起動してください。" -#: src/iac_code/ui/repl.py:568 +#: src/iac_code/ui/repl.py:570 msgid "No image in clipboard." msgstr "クリップボードに画像がありません。" -#: src/iac_code/ui/repl.py:754 +#: src/iac_code/ui/repl.py:756 msgid "Usage: !" msgstr "使用方法: !" -#: src/iac_code/ui/repl.py:759 +#: src/iac_code/ui/repl.py:761 msgid "Shell command support is unavailable." msgstr "シェルコマンドのサポートは利用できません。" -#: src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "不明なスキル: ${name}。/ を入力するとコマンドとスキルを一覧表示します。" -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:830 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." -msgstr "Unknown command: /{name}. Type /help for available commands.不明なコマンドです:/{name}。利用可能なコマンドは /help を入力してください。" +msgstr "" +"Unknown command: /{name}. Type /help for available " +"commands.不明なコマンドです:/{name}。利用可能なコマンドは /help を入力してください。" -#: src/iac_code/ui/repl.py:833 +#: src/iac_code/ui/repl.py:835 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ はスキルのみを呼び出します。代わりに /{name} を使用してください。" -#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 +#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 #, python-brace-format msgid "Command error: {error}" msgstr "コマンドエラー:{error}" -#: src/iac_code/ui/repl.py:862 +#: src/iac_code/ui/repl.py:864 #, python-brace-format msgid "Command has no handler: {name}" msgstr "ハンドラーがないコマンドです:{name}" -#: src/iac_code/ui/repl.py:1167 +#: src/iac_code/ui/repl.py:1126 +msgid "Goodbye!" +msgstr "さようなら。" + +#: src/iac_code/ui/repl.py:1127 +msgid "Resume this session with:" +msgstr "このセッションを再開するには次を実行してください:" + +#: src/iac_code/ui/repl.py:1130 +msgid "Session ID" +msgstr "セッション ID" + +#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 #, python-brace-format msgid "Session not found: {session_id}" msgstr "セッションが見つかりません:{session_id}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1233 +msgid "Session name: " +msgstr "セッション名: " + +#: src/iac_code/ui/repl.py:1239 +msgid "Session name cannot be empty." +msgstr "セッション名は空にできません。" + +#: src/iac_code/ui/repl.py:1251 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2184,30 +2384,38 @@ msgstr "" "再開するには次を実行してください:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1287 +#: src/iac_code/ui/repl.py:1257 +msgid "Multiple sessions match. Resume one by ID:" +msgstr "複数のセッションが一致しました。ID でいずれかを再開してください:" + +#: src/iac_code/ui/repl.py:1370 msgid "This conversation is from a different directory." msgstr "この会話は別のディレクトリ由来です。" -#: src/iac_code/ui/repl.py:1289 +#: src/iac_code/ui/repl.py:1372 msgid "To resume, run:" msgstr "再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:1294 +#: src/iac_code/ui/repl.py:1377 msgid "(Command copied to clipboard)" msgstr "(コマンドをクリップボードにコピーしました)" -#: src/iac_code/ui/repl.py:1451 +#: src/iac_code/ui/repl.py:1534 #, python-brace-format -msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." +msgid "" +"Current model {model} does not support image input. Use /model to switch " +"to a vision-capable model." msgstr "現在のモデル {model} は画像入力をサポートしていません。/model を使用してビジョン対応モデルに切り替えてください。" -#: src/iac_code/ui/repl.py:1460 +#: src/iac_code/ui/repl.py:1543 #, python-brace-format msgid "Image error: {err}" msgstr "画像エラー:{err}" -#: src/iac_code/ui/repl.py:1477 -msgid "Failed to persist image to cache; it will only exist in memory for this turn." +#: src/iac_code/ui/repl.py:1560 +msgid "" +"Failed to persist image to cache; it will only exist in memory for this " +"turn." msgstr "画像をキャッシュに保存できませんでした。このターンの間、メモリ上にのみ存在します。" #: src/iac_code/ui/spinner.py:52 @@ -2298,87 +2506,87 @@ msgstr "入力してファイルを検索…" msgid "No matching files" msgstr "一致するファイルがありません" -#: src/iac_code/ui/dialogs/resume_picker.py:114 +#: src/iac_code/ui/dialogs/resume_picker.py:116 msgid "Search..." msgstr "検索…" -#: src/iac_code/ui/dialogs/resume_picker.py:359 +#: src/iac_code/ui/dialogs/resume_picker.py:374 msgid "Resume Session" msgstr "セッションを再開" -#: src/iac_code/ui/dialogs/resume_picker.py:369 +#: src/iac_code/ui/dialogs/resume_picker.py:384 msgid "No sessions found" msgstr "セッションが見つかりません" -#: src/iac_code/ui/dialogs/resume_picker.py:427 +#: src/iac_code/ui/dialogs/resume_picker.py:444 msgid "show current dir" msgstr "現在のディレクトリのみ表示" -#: src/iac_code/ui/dialogs/resume_picker.py:429 +#: src/iac_code/ui/dialogs/resume_picker.py:446 msgid "show all projects" msgstr "すべてのプロジェクトを表示" -#: src/iac_code/ui/dialogs/resume_picker.py:432 +#: src/iac_code/ui/dialogs/resume_picker.py:449 msgid "show all branches" msgstr "すべてのブランチを表示" -#: src/iac_code/ui/dialogs/resume_picker.py:434 +#: src/iac_code/ui/dialogs/resume_picker.py:451 msgid "only show current branch" msgstr "現在のブランチのみ表示" -#: src/iac_code/ui/dialogs/resume_picker.py:435 +#: src/iac_code/ui/dialogs/resume_picker.py:452 msgid "preview" msgstr "プレビュー" -#: src/iac_code/ui/dialogs/resume_picker.py:436 +#: src/iac_code/ui/dialogs/resume_picker.py:453 msgid "Type to search" msgstr "入力して検索" -#: src/iac_code/ui/dialogs/resume_picker.py:437 +#: src/iac_code/ui/dialogs/resume_picker.py:454 msgid "cancel" msgstr "キャンセル" -#: src/iac_code/ui/dialogs/resume_picker.py:552 +#: src/iac_code/ui/dialogs/resume_picker.py:569 #, python-brace-format msgid "{n} more line{s}" msgstr "あと {n} 行{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:566 +#: src/iac_code/ui/dialogs/resume_picker.py:583 #, python-brace-format msgid "{n} message{s}" msgstr "{n} 件のメッセージ{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:580 +#: src/iac_code/ui/dialogs/resume_picker.py:597 msgid "resume" msgstr "再開" -#: src/iac_code/ui/dialogs/resume_picker.py:584 +#: src/iac_code/ui/dialogs/resume_picker.py:601 msgid "back" msgstr "戻る" -#: src/iac_code/ui/dialogs/resume_picker.py:589 +#: src/iac_code/ui/dialogs/resume_picker.py:606 msgid "scroll" msgstr "スクロール" -#: src/iac_code/ui/dialogs/resume_picker.py:608 +#: src/iac_code/ui/dialogs/resume_picker.py:625 msgid "(empty session)" msgstr "(空のセッション)" -#: src/iac_code/ui/dialogs/resume_picker.py:728 +#: src/iac_code/ui/dialogs/resume_picker.py:745 msgid "just now" msgstr "たった今" -#: src/iac_code/ui/dialogs/resume_picker.py:731 +#: src/iac_code/ui/dialogs/resume_picker.py:748 #, python-brace-format msgid "{n} minute{s} ago" msgstr "{n} 分{s}前" -#: src/iac_code/ui/dialogs/resume_picker.py:734 +#: src/iac_code/ui/dialogs/resume_picker.py:751 #, python-brace-format msgid "{n} hour{s} ago" msgstr "{n} 時間{s}前" -#: src/iac_code/ui/dialogs/resume_picker.py:736 +#: src/iac_code/ui/dialogs/resume_picker.py:753 #, python-brace-format msgid "{n} day{s} ago" msgstr "{n} 日{s}前" @@ -2398,7 +2606,9 @@ msgstr "{total} 件中 {current}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" msgstr "{count} 個のスキル - Space で切り替え、Enter で保存、Tab で並べ替え、Esc でキャンセル" #: src/iac_code/ui/dialogs/skills_picker.py:171 @@ -2485,7 +2695,9 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code を Windows で使用するには Git for Windows が必要です。" #: src/iac_code/utils/platform.py:41 -msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." +msgid "" +"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " +"variable." msgstr "インストール済みで PATH に含まれていない場合は、IAC_CODE_GIT_BASH_PATH 環境変数を設定してください。" #: src/iac_code/utils/platform.py:44 @@ -2497,7 +2709,9 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " 方法 1 - winget(github.com へのアクセスが必要):" #: src/iac_code/utils/platform.py:52 -msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" +msgid "" +" Option 2 - if you cannot reach github.com, run this to install via " +"npmmirror:" msgstr " オプション 2 - github.com にアクセスできない場合は、npmmirror 経由でインストールするために以下を実行してください:" #~ msgid "toggle preview" @@ -2521,8 +2735,13 @@ msgstr " オプション 2 - github.com にアクセスできない場合は、 #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." -#~ msgstr "LLM プロバイダーは '{source}' によりロックされています。変更するには settings.yml の llm_source を修正してください。" +#~ msgid "" +#~ "LLM provider is locked by '{source}'." +#~ " To change, modify llm_source in " +#~ "settings.yml." +#~ msgstr "" +#~ "LLM プロバイダーは '{source}' によりロックされています。変更するには " +#~ "settings.yml の llm_source を修正してください。" #~ msgid "Provider switched: {status}" #~ msgstr "Provider が切り替わりました: {status}" @@ -2539,6 +2758,12 @@ msgstr " オプション 2 - github.com にアクセスできない場合は、 #~ msgid "Cache create" #~ msgstr "キャッシュ作成" -#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" #~ msgstr "{count} 個のスキル - Space で切り替え、Enter で保存、/ で検索、t で並べ替え、Esc でキャンセル" +#~ msgid "Resume a session by ID" +#~ msgstr "ID でセッションを再開する" + 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 0cbbac9..6c2494a 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 21:26+0800\n" +"POT-Creation-Date: 2026-06-02 22:08+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: pt\n" @@ -20,84 +20,133 @@ msgstr "" #: src/iac_code/config.py:164 #, python-brace-format msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}" -msgstr "Valor inválido de IAC_CODE_PROVIDER: {!r}. Valores válidos (sem diferenciar maiúsculas de minúsculas): {}" +msgstr "" +"Valor inválido de IAC_CODE_PROVIDER: {!r}. Valores válidos (sem " +"diferenciar maiúsculas de minúsculas): {}" #: src/iac_code/a2a/transports/base.py:175 -msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." -msgstr "O transporte de socket de domínio Unix não é suportado no Windows. Use --transport http ou --transport stdio." +msgid "" +"Unix domain socket transport is not supported on Windows. Use --transport" +" http or --transport stdio instead." +msgstr "" +"O transporte de socket de domínio Unix não é suportado no Windows. Use " +"--transport http ou --transport stdio." + +#: src/iac_code/acp/server.py:413 src/iac_code/acp/server.py:429 +#: src/iac_code/acp/server.py:456 +msgid "Session not found" +msgstr "Sessão não encontrada" -#: src/iac_code/acp/slash_registry.py:44 +#: src/iac_code/acp/server.py:416 #, python-brace-format -msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" -msgstr "O comando '/{cmd_name}' não é compatível com ACP. Comandos compatíveis: {supported}" +msgid "Session name is ambiguous. Candidates: {candidates}" +msgstr "O nome da sessão é ambíguo. Candidatas: {candidates}" -#: src/iac_code/acp/slash_registry.py:58 +#: src/iac_code/acp/server.py:434 src/iac_code/acp/server.py:708 +#, python-brace-format +msgid "Session belongs to another project. Run: {hint}" +msgstr "A sessão pertence a outro projeto. Execute: {hint}" + +#: src/iac_code/acp/slash_registry.py:46 +#, python-brace-format +msgid "" +"Command '/{cmd_name}' is not supported over ACP. Supported commands: " +"{supported}" +msgstr "" +"O comando '/{cmd_name}' não é compatível com ACP. Comandos compatíveis: " +"{supported}" + +#: src/iac_code/acp/slash_registry.py:62 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "Tratador do comando '/{cmd_name}' não implementado." -#: src/iac_code/acp/slash_registry.py:70 +#: src/iac_code/acp/slash_registry.py:74 #, python-brace-format msgid "Compaction failed: {error}" msgstr "Falha ao compactar: {error}" -#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:77 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "Nada para compactar: a conversa está vazia." -#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." +msgid "" +"Conversation too short to compact: all messages are within the recent " +"{turns}-turn preservation window." msgstr "" -"Conversa curta demais para compactar: todas as mensagens estão na janela de preservação das últimas {turns} " -"interações." +"Conversa curta demais para compactar: todas as mensagens estão na janela " +"de preservação das últimas {turns} interações." -#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:84 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "Falha na compactação. Consulte os logs para detalhes." -#: src/iac_code/acp/slash_registry.py:85 +#: src/iac_code/acp/slash_registry.py:89 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" -msgstr "Contexto compactado: {original} → {compacted} tokens (redução de {percent}). Uso do contexto: {usage}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent} reduction)." +" Context usage: {usage}" +msgstr "" +"Contexto compactado: {original} → {compacted} tokens (redução de " +"{percent}). Uso do contexto: {usage}" -#: src/iac_code/acp/slash_registry.py:99 +#: src/iac_code/acp/slash_registry.py:103 #, python-brace-format msgid "Clear failed: {error}" msgstr "Falha ao limpar: {error}" -#: src/iac_code/acp/slash_registry.py:100 +#: src/iac_code/acp/slash_registry.py:104 msgid "Conversation history cleared." msgstr "Histórico da conversa limpo." -#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:120 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "Debug ativado. Arquivo de log: {path}" -#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "Debug desativado." -#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "Debug ativado. Arquivo de log: {path}" -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:129 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "Debug desativado." -#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:131 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "Uso: /debug [on|off]" -#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +#: src/iac_code/acp/slash_registry.py:136 src/iac_code/commands/memory.py:84 msgid "Memory manager is unavailable." msgstr "O gerenciador de memória está indisponível." -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 -#: src/iac_code/ui/repl.py:807 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +msgid "Usage: /rename " +msgstr "Uso: /rename " + +#: src/iac_code/acp/slash_registry.py:152 +msgid "Rename is only available after a session is created." +msgstr "Renomear só fica disponível depois que uma sessão é criada." + +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#, python-brace-format +msgid "Session is already named {name}" +msgstr "A sessão já se chama {name}" + +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#, python-brace-format +msgid "Renamed session to {name}" +msgstr "Sessão renomeada para {name}" + +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 msgid "Permission denied." msgstr "Permissão negada." @@ -114,7 +163,8 @@ msgstr "Explorar" msgid "Plan" msgstr "Planejar" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 +#: src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "Agent" @@ -166,14 +216,16 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-" +"code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " Correção: execute iac-code e digite /auth\n" " ou defina a variável de ambiente: IAC_CODE_API_KEY=\n" -" Documentação: https://aliyun.github.io/iac-code/pt/docs/configuration/authentication\n" +" Documentação: https://aliyun.github.io/iac-" +"code/pt/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -186,7 +238,9 @@ msgstr "Instalando Git for Windows via npmmirror..." #: src/iac_code/cli/install_git_bash.py:59 msgid "powershell.exe was not found on PATH; cannot run installer." -msgstr "powershell.exe não foi encontrado no PATH; não é possível executar o instalador." +msgstr "" +"powershell.exe não foi encontrado no PATH; não é possível executar o " +"instalador." #: src/iac_code/cli/install_git_bash.py:66 #, python-brace-format @@ -195,11 +249,11 @@ msgstr "A instalação falhou (PowerShell saiu com código {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " -"non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may " +"have been cancelled or the installer used a non-standard path." msgstr "" -"O instalador encerrou mas bash.exe não foi encontrado em locais comuns; UAC pode ter sido cancelado ou o instalador " -"usou um caminho não padrão." +"O instalador encerrou mas bash.exe não foi encontrado em locais comuns; " +"UAC pode ter sido cancelado ou o instalador usou um caminho não padrão." #: src/iac_code/cli/install_git_bash.py:84 #, python-brace-format @@ -222,9 +276,14 @@ msgstr "Instalar Git for Windows pelo espelho npmmirror (somente Windows)." msgid "YAML config file containing A2A client options" msgstr "Arquivo de configuração YAML com opções do cliente A2A" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" -msgstr "As dependências do cliente A2A estão ausentes. Instale com: pip install 'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 +#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "" +"A2A client dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" +msgstr "" +"As dependências do cliente A2A estão ausentes. Instale com: pip install " +"'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 msgid "LLM model to use" @@ -242,7 +301,8 @@ msgstr "Formato de saída: text, json, stream-json" msgid "Maximum agent turns in headless mode" msgstr "Número máximo de passos do agent em modo headless" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 +#: src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "Ativar debug" @@ -255,8 +315,8 @@ msgid "Show version and exit" msgstr "Mostrar versão e sair" #: src/iac_code/cli/main.py:89 -msgid "Resume a session by ID" -msgstr "Retomar uma sessão pelo ID" +msgid "Resume a session by ID or name" +msgstr "Retomar uma sessão por ID ou nome" #: src/iac_code/cli/main.py:90 msgid "Resume the most recent session" @@ -267,12 +327,20 @@ msgid "Install completion for the current shell." msgstr "Instalar completion para o shell atual." #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "Show completion for the current shell, to copy it or customize the installation." -msgstr "Exibir o completion do shell atual, para copiar ou personalizar a instalação." +msgid "" +"Show completion for the current shell, to copy it or customize the " +"installation." +msgstr "" +"Exibir o completion do shell atual, para copiar ou personalizar a " +"instalação." #: src/iac_code/cli/main.py:110 -msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" -msgstr "Padrões de permissão de ferramentas a permitir (separados por vírgula), ex. 'bash(git *),write_file'*),write_file'" +msgid "" +"Comma-separated tool permission patterns to allow, e.g. 'bash(git " +"*),write_file'" +msgstr "" +"Padrões de permissão de ferramentas a permitir (separados por vírgula), " +"ex. 'bash(git *),write_file'*),write_file'" #: src/iac_code/cli/main.py:115 msgid "Comma-separated tool permission patterns to deny" @@ -316,27 +384,45 @@ msgid "YAML config file for A2A server options" msgstr "Arquivo de configuração YAML para opções do servidor A2A" #: src/iac_code/cli/main.py:584 -msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." -msgstr "Porta do servidor HTTP. 41242 é o padrão do iac-code inspirado no Gemini CLI, não uma porta A2A registrada." +msgid "" +"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " +"not a registered A2A port." +msgstr "" +"Porta do servidor HTTP. 41242 é o padrão do iac-code inspirado no Gemini " +"CLI, não uma porta A2A registrada." #: src/iac_code/cli/main.py:589 -msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" -msgstr "Transporte A2A: http, stdio, unix, websocket, grpc, grpc-jsonrpc ou redis-streams" +msgid "" +"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " +"redis-streams" +msgstr "" +"Transporte A2A: http, stdio, unix, websocket, grpc, grpc-jsonrpc ou " +"redis-streams" #: src/iac_code/cli/main.py:595 -msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." -msgstr "Expõe tipos de sinal de thinking A2A; repita para múltiplos. Valores: raw-thinking, tool-trace." +msgid "" +"Expose A2A thinking signal types; repeat for multiple. Values: raw-" +"thinking, tool-trace." +msgstr "" +"Expõe tipos de sinal de thinking A2A; repita para múltiplos. Valores: " +"raw-thinking, tool-trace." #: src/iac_code/cli/main.py:646 -msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" -msgstr "As dependências do servidor A2A estão ausentes. Instale com: pip install 'iac-code[a2a]'" +msgid "" +"A2A server dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" +msgstr "" +"As dependências do servidor A2A estão ausentes. Instale com: pip install " +"'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "Envia um prompt para um endpoint JSON-RPC A2A." -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 +#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 +#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "URL do endpoint JSON-RPC A2A" @@ -361,33 +447,48 @@ msgstr "Metadados do diretório de trabalho a enviar com a requisição" msgid "A2A context ID to continue" msgstr "ID de contexto A2A para continuar" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 +#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 +#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 +#: src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "Token Bearer para requisições HTTP A2A" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 +#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 +#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 +#: src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "Nome de usuário de autenticação básica para requisições HTTP A2A" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 +#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 +#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 +#: src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "Senha de autenticação básica para requisições HTTP A2A" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 +#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 +#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 +#: src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "Chave de API para requisições HTTP A2A" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 +#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 +#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 +#: src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "Nome do cabeçalho HTTP para a chave de API A2A" @@ -423,8 +524,10 @@ msgstr "URL base do agente A2A" msgid "Get an A2A task." msgstr "Obtém uma tarefa A2A." -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 +#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 +#: src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "ID da tarefa A2A" @@ -472,7 +575,8 @@ msgstr "Assina um fluxo de eventos de tarefa A2A." msgid "Create an A2A task push notification config." msgstr "Cria uma configuração de notificação push de tarefa A2A." -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 +#: src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "ID da configuração push" @@ -536,81 +640,89 @@ msgstr "Diretório para rotas A2A persistidas" msgid "Save the provided routes as a route snapshot" msgstr "Salva as rotas fornecidas como um snapshot de rotas" -#: src/iac_code/commands/__init__.py:25 +#: src/iac_code/commands/__init__.py:26 msgid "Show available commands" msgstr "Mostrar comandos disponíveis" -#: src/iac_code/commands/__init__.py:34 +#: src/iac_code/commands/__init__.py:35 msgid "Clear conversation history" msgstr "Limpar histórico da conversa" -#: src/iac_code/commands/__init__.py:42 +#: src/iac_code/commands/__init__.py:43 msgid "Show or switch model" msgstr "Exibir ou trocar modelo" -#: src/iac_code/commands/__init__.py:51 +#: src/iac_code/commands/__init__.py:52 msgid "Show or switch thinking effort" msgstr "Exibir ou alterar o nível de esforço de raciocínio" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compact conversation context" msgstr "Compactar contexto da conversa" -#: src/iac_code/commands/__init__.py:62 +#: src/iac_code/commands/__init__.py:63 msgid "Compacting conversation" msgstr "Compactando conversa" -#: src/iac_code/commands/__init__.py:69 +#: src/iac_code/commands/__init__.py:70 msgid "Exit the application" msgstr "Encerrar o aplicativo" -#: src/iac_code/commands/__init__.py:78 +#: src/iac_code/commands/__init__.py:79 msgid "Authenticate with LLM provider" msgstr "Autenticar com o provedor LLM" -#: src/iac_code/commands/__init__.py:87 +#: src/iac_code/commands/__init__.py:88 msgid "Toggle debug logging" msgstr "Alternar debug" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "View and manage persistent memories" msgstr "Ver e gerenciar memórias persistentes" -#: src/iac_code/commands/__init__.py:98 +#: src/iac_code/commands/__init__.py:99 msgid "[|search |delete |help]" msgstr "[|search |delete |help]" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "Resume a previous session" msgstr "Retomar uma sessão anterior" -#: src/iac_code/commands/__init__.py:107 +#: src/iac_code/commands/__init__.py:108 msgid "[conversation id or search term]" msgstr "[ID da conversa ou termo de busca]" -#: src/iac_code/commands/__init__.py:114 +#: src/iac_code/commands/__init__.py:115 +msgid "Rename the current session" +msgstr "Renomear a sessão atual" + +#: src/iac_code/commands/__init__.py:124 msgid "Manage skills" msgstr "Gerenciar habilidades" -#: src/iac_code/commands/__init__.py:122 +#: src/iac_code/commands/__init__.py:132 msgid "Show current session status" msgstr "Mostrar o status atual da sessão" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navegar" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 -#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmar" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 -#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 -#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 +#: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 +#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "Voltar" @@ -622,8 +734,9 @@ msgstr "Manter" msgid "Re-enter" msgstr "Digitar novamente" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 +#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 +#: src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (atual)" @@ -660,17 +773,20 @@ msgstr "Configurar serviço de nuvem IaC" msgid "Select configuration type" msgstr "Selecionar tipo de configuração" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 +#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 +#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "Autenticação cancelada" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 +#: src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "Selecionar provedor — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 +#: src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "Terceiros" @@ -766,8 +882,9 @@ msgstr "Selecionar provedor de nuvem" msgid "Credential" msgstr "Credencial" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 -#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 +#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Região" @@ -902,8 +1019,12 @@ msgstr "Nenhum agent loop ativo." #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" -msgstr "Contexto compactado: {original} → {compacted} tokens (redução de {percent_display}). Uso do contexto: {usage_display}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent_display} " +"reduction). Context usage: {usage_display}" +msgstr "" +"Contexto compactado: {original} → {compacted} tokens (redução de " +"{percent_display}). Uso do contexto: {usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -913,7 +1034,8 @@ msgstr "O comando debug requer um contexto." msgid "No active session." msgstr "Nenhuma sessão ativa." -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 +#: src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "Nenhum provedor configurado. Execute /auth primeiro." @@ -1007,8 +1129,12 @@ msgstr "Memória '{name}' excluída." #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." -msgstr "O modelo é gerenciado por '{source}'. Para alterá-lo, modifique em {source} ou troque de provedor via /auth." +msgid "" +"Model is managed by '{source}'. To change model, modify it in {source} or" +" switch provider via /auth." +msgstr "" +"O modelo é gerenciado por '{source}'. Para alterá-lo, modifique em " +"{source} ou troque de provedor via /auth." #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 #, python-brace-format @@ -1025,23 +1151,36 @@ msgstr "Modelo atual: {model}" msgid "Kept model as {model}" msgstr "Modelo mantido como {model}" -#: src/iac_code/commands/resume.py:21 +#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +msgid "Rename is only available in interactive mode." +msgstr "Renomear só está disponível no modo interativo." + +#: src/iac_code/commands/rename.py:30 +msgid "Rename cancelled" +msgstr "Renomeação cancelada" + +#: src/iac_code/commands/resume.py:22 msgid "Resume is only available in interactive mode." msgstr "Retomar está disponível apenas no modo interativo." -#: src/iac_code/commands/resume.py:27 +#: src/iac_code/commands/resume.py:28 msgid "Resume is unavailable: session index not initialised." msgstr "Não é possível retomar: índice de sessões não inicializado." -#: src/iac_code/commands/resume.py:32 +#: src/iac_code/commands/resume.py:33 src/iac_code/commands/resume.py:36 #, python-brace-format msgid "Session not found: {arg}" msgstr "Sessão não encontrada: {arg}" -#: src/iac_code/commands/resume.py:47 +#: src/iac_code/commands/resume.py:52 src/iac_code/commands/resume.py:68 msgid "Resume cancelled" msgstr "Retomada cancelada" +#: src/iac_code/commands/resume.py:55 +#, python-brace-format +msgid "Unable to resolve session: {arg}" +msgstr "Não foi possível resolver a sessão: {arg}" + #: src/iac_code/commands/skills.py:14 msgid "Skills management is only available in interactive mode." msgstr "O gerenciamento de habilidades só está disponível no modo interativo." @@ -1066,7 +1205,8 @@ msgstr "O comando status requer um contexto REPL." msgid "Status is only available in interactive mode." msgstr "status está disponível apenas no modo interativo." -#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:136 +#: src/iac_code/ui/banner.py:138 msgid "Session" msgstr "Sessão" @@ -1074,7 +1214,8 @@ msgstr "Sessão" msgid "Provider" msgstr "Provedor" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "não configurado" @@ -1175,8 +1316,10 @@ msgstr "Abortado!" #, python-brace-format msgid "Cannot determine provider for model: {model}. Run /auth to configure." msgstr "" -"Cannot determine provider for model: {model}. Run /auth to configure.Cannot determine provider for model: {model}. " -"Run /auth to configure.Não é possível determinar o provedor para o modelo: {model}. Execute /auth para configurar." +"Cannot determine provider for model: {model}. Run /auth to " +"configure.Cannot determine provider for model: {model}. Run /auth to " +"configure.Não é possível determinar o provedor para o modelo: {model}. " +"Execute /auth para configurar." #: src/iac_code/providers/manager.py:95 #, python-brace-format @@ -1185,26 +1328,34 @@ msgstr "Chave de provedor desconhecida: '{key}'. Execute /auth para configurar." #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." -msgstr "Nenhuma chave API configurada para o provedor '{provider}' (modelo: {model}). Execute /auth para configurar." +msgid "" +"No API key configured for provider '{provider}' (model: {model}). Run " +"/auth to configure." +msgstr "" +"Nenhuma chave API configurada para o provedor '{provider}' (modelo: " +"{model}). Execute /auth para configurar." #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " -"endpoints require a /v1 suffix (e.g. {base_url}/v1)." +"API returned no data. Please check that your API Base URL is correct " +"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " +"suffix (e.g. {base_url}/v1)." msgstr "" -"A API não retornou dados. Verifique se a API Base URL está correta (atual: {base_url}). Muitos endpoints compatíveis " -"com OpenAI exigem o sufixo /v1 (por exemplo, {base_url}/v1)." +"A API não retornou dados. Verifique se a API Base URL está correta " +"(atual: {base_url}). Muitos endpoints compatíveis com OpenAI exigem o " +"sufixo /v1 (por exemplo, {base_url}/v1)." #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" -"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." +"API returned an invalid response. Please check that your API Base URL is " +"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" +" /v1 suffix (e.g. {base_url}/v1)." msgstr "" -"A API retornou uma resposta inválida. Verifique se a API Base URL está correta (atual: {base_url}). Muitos endpoints " -"compatíveis com OpenAI exigem o sufixo /v1 (por exemplo, {base_url}/v1)." +"A API retornou uma resposta inválida. Verifique se a API Base URL está " +"correta (atual: {base_url}). Muitos endpoints compatíveis com OpenAI " +"exigem o sufixo /v1 (por exemplo, {base_url}/v1)." #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1285,22 +1436,35 @@ msgstr "Compatível com Anthropic" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " +"support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " -"settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " +"mode (remove 'llm_source: qwenpaw' from settings.yml)." msgstr "" -"[Modo QwenPaw] Provider desconhecido '{provider_id}'. iac-code não suporta este provider.\n" +"[Modo QwenPaw] Provider desconhecido '{provider_id}'. iac-code não " +"suporta este provider.\n" "IDs de providers QwenPaw suportados: {supported_ids}\n" -"Solução: mude para um provider suportado no QwenPaw, ou desative o modo QwenPaw (remova 'llm_source: qwenpaw' do " -"settings.yml)." +"Solução: mude para um provider suportado no QwenPaw, ou desative o modo " +"QwenPaw (remova 'llm_source: qwenpaw' do settings.yml)." + +#: src/iac_code/services/session_metadata.py:52 +#, python-brace-format +msgid "Session name must match {pattern}" +msgstr "O nome da sessão deve corresponder a {pattern}" + +#: src/iac_code/services/session_storage.py:241 +#, python-brace-format +msgid "Session name already exists in this project: {name}" +msgstr "O nome da sessão já existe neste projeto: {name}" #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "--permission-mode inválido {!r}. Valores válidos: {}" -#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 +#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "Permitir {}?" @@ -1368,28 +1532,38 @@ msgid "Waiting for browser authorization" msgstr "Aguardando autorização do navegador" #: src/iac_code/services/providers/aliyun_oauth.py:300 -msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." -msgstr "1. O navegador pode mostrar official-cli; este é o aplicativo OAuth oficial da CLI da Alibaba Cloud." +msgid "" +"1. The browser may show official-cli; this is the Alibaba Cloud official " +"CLI OAuth application." +msgstr "" +"1. O navegador pode mostrar official-cli; este é o aplicativo OAuth " +"oficial da CLI da Alibaba Cloud." #: src/iac_code/services/providers/aliyun_oauth.py:302 -msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgid "" +"2. If assignment is required, assign the RAM user or RAM role that is " +"signed in. User groups are not supported." msgstr "" -"2. Se a atribuição for necessária, atribua o usuário RAM ou a função RAM que está conectada. Grupos de usuários não " -"são compatíveis." +"2. Se a atribuição for necessária, atribua o usuário RAM ou a função RAM " +"que está conectada. Grupos de usuários não são compatíveis." #: src/iac_code/services/providers/aliyun_oauth.py:306 msgid "" -"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " -"out of Alibaba Cloud and sign in again." +"3. After assignment, close the old authorization page and run OAuth Login" +" (Browser) again. If it still fails, sign out of Alibaba Cloud and sign " +"in again." msgstr "" -"3. Após a atribuição, feche a página de autorização antiga e execute Login OAuth (navegador) novamente. Se ainda " -"falhar, saia da Alibaba Cloud e entre novamente." +"3. Após a atribuição, feche a página de autorização antiga e execute " +"Login OAuth (navegador) novamente. Se ainda falhar, saia da Alibaba Cloud" +" e entre novamente." #: src/iac_code/services/providers/aliyun_oauth.py:310 -msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgid "" +"4. STS credentials refresh when possible until Alibaba Cloud expires " +"them. If refresh fails, run /auth again." msgstr "" -"4. As credenciais STS são atualizadas quando possível até expirarem na Alibaba Cloud. Se a atualização falhar, " -"execute /auth novamente." +"4. As credenciais STS são atualizadas quando possível até expirarem na " +"Alibaba Cloud. Se a atualização falhar, execute /auth novamente." #: src/iac_code/services/providers/aliyun_oauth.py:313 msgid "Press Esc to cancel while waiting." @@ -1397,16 +1571,20 @@ msgstr "Pressione Esc para cancelar a espera." #: src/iac_code/services/providers/aliyun_oauth.py:321 msgid "" -"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" -" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " -"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to " +"assign the official-cli application, assign it to the exact RAM user or " +"RAM role currently signed in. User groups are not supported. Then close " +"the old authorization page, sign out of Alibaba Cloud and sign in again " +"if needed, and run /auth to choose OAuth Login (Browser) again." msgstr "" -"Tempo esgotado aguardando o callback OAuth. Se a Alibaba Cloud pediu para atribuir o aplicativo official-cli, " -"atribua-o ao usuário RAM ou à função RAM exata atualmente conectada. Grupos de usuários não são compatíveis. Depois " -"feche a página de autorização antiga, saia da Alibaba Cloud e entre novamente se necessário, e execute /auth para " -"escolher Login OAuth (navegador) novamente." +"Tempo esgotado aguardando o callback OAuth. Se a Alibaba Cloud pediu para" +" atribuir o aplicativo official-cli, atribua-o ao usuário RAM ou à função" +" RAM exata atualmente conectada. Grupos de usuários não são compatíveis. " +"Depois feche a página de autorização antiga, saia da Alibaba Cloud e " +"entre novamente se necessário, e execute /auth para escolher Login OAuth " +"(navegador) novamente." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "A habilidade '{name}' está desativada. Execute /skills para ativá-la." @@ -1426,10 +1604,12 @@ msgid "Skill disabled: {name}" msgstr "Habilidade desativada: {name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." +msgid "" +"Review changed code for reuse, quality, and efficiency, then fix issues " +"found." msgstr "" -"Revise o código alterado quanto à reutilização, qualidade e eficiência e, em seguida, corrija os problemas " -"encontrados." +"Revise o código alterado quanto à reutilização, qualidade e eficiência e," +" em seguida, corrija os problemas encontrados." #: src/iac_code/tools/edit_file.py:116 msgid "Edit" @@ -1527,8 +1707,8 @@ msgstr "A URL não pode estar vazia." #, python-brace-format msgid "Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}" msgstr "" -"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}URL inválida: esquema ausente (ex.: http:// ou " -"https://). Recebido: {url}" +"Invalid URL: missing scheme (e.g. http:// or https://). Got: {url}URL " +"inválida: esquema ausente (ex.: http:// ou https://). Recebido: {url}" #: src/iac_code/tools/web_fetch.py:103 #, python-brace-format @@ -1637,7 +1817,8 @@ msgstr "Regra(s) de negação correspondente(s): {}" msgid "matched ask rule(s): {}" msgstr "Regra(s) de consulta correspondente(s): {}" -#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 +#: src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "Regra(s) de permissão correspondente(s): {}" @@ -1694,7 +1875,8 @@ msgstr "API na nuvem" msgid "Calling {action}..." msgstr "Chamando {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 +#: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "Chamada bem-sucedida" @@ -1707,7 +1889,8 @@ msgstr "Resposta recebida ({count} linhas)" msgid "CloudStack" msgstr "CloudStack" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 +#: src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "Executando {action}..." @@ -1913,8 +2096,8 @@ msgstr "{count} documentos encontrados (total {total})" #: src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py:143 msgid "Use web_fetch tool to read full document content if needed." msgstr "" -"Use web_fetch tool to read full document content if needed.Se necessário, use a ferramenta web_fetch para ler o " -"conteúdo completo." +"Use web_fetch tool to read full document content if needed.Se necessário," +" use a ferramenta web_fetch para ler o conteúdo completo." #: src/iac_code/tools/cloud/aliyun/ros_stack.py:143 msgid "ROS Stack" @@ -1947,12 +2130,20 @@ msgstr "Erro de sintaxe JSON no modelo (linha {line}, coluna {col}): {msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "Template {fmt} parse result is not an object (dict), please check the template format" -msgstr "O resultado da análise {fmt} do modelo não é um objeto (dict), por favor verifique o formato do modelo" +msgid "" +"Template {fmt} parse result is not an object (dict), please check the " +"template format" +msgstr "" +"O resultado da análise {fmt} do modelo não é um objeto (dict), por favor " +"verifique o formato do modelo" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" -msgstr "O modelo não contém ROSTemplateFormatVersion (modelos ROS devem incluir este campo, ex. '2015-09-01')" +msgid "" +"Template is missing ROSTemplateFormatVersion (ROS templates must include " +"this field, e.g. '2015-09-01')" +msgstr "" +"O modelo não contém ROSTemplateFormatVersion (modelos ROS devem incluir " +"este campo, ex. '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 msgid "Template is missing Resources (ROS templates must include Resources)" @@ -1965,8 +2156,12 @@ msgstr "Resources deve ser um objeto (dict), o tipo atual é {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "Resource '{name}' definition must be an object (dict), current type is {type}" -msgstr "A definição do recurso '{name}' deve ser um objeto (dict), o tipo atual é {type}" +msgid "" +"Resource '{name}' definition must be an object (dict), current type is " +"{type}" +msgstr "" +"A definição do recurso '{name}' deve ser um objeto (dict), o tipo atual é" +" {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 #, python-brace-format @@ -1976,11 +2171,17 @@ msgstr "O recurso '{name}' não possui o campo Type" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:129 #, python-brace-format msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" -msgstr "O recurso '{name}' possui o tipo incorreto '{wrong}', deveria ser '{correct}'" +msgstr "" +"O recurso '{name}' possui o tipo incorreto '{wrong}', deveria ser " +"'{correct}'" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "Template structure validation found the following issues, please fix and retry:" -msgstr "A validação da estrutura do modelo encontrou os seguintes problemas, por favor corrija e tente novamente:" +msgid "" +"Template structure validation found the following issues, please fix and " +"retry:" +msgstr "" +"A validação da estrutura do modelo encontrou os seguintes problemas, por " +"favor corrija e tente novamente:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 #, python-brace-format @@ -2000,28 +2201,30 @@ msgstr "Notas da versão" msgid "Run {} to update." msgstr "Execute {} para atualizar." -#: src/iac_code/ui/banner.py:105 +#: src/iac_code/ui/banner.py:110 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "Seu assistente de Infrastructure as Code com IA" -#: src/iac_code/ui/banner.py:131 +#: src/iac_code/ui/banner.py:144 msgid "Welcome back" msgstr "Bem-vindo de volta" -#: src/iac_code/ui/banner.py:148 +#: src/iac_code/ui/banner.py:161 msgid "Debug mode" msgstr "Modo debug" -#: src/iac_code/ui/banner.py:149 +#: src/iac_code/ui/banner.py:162 msgid "Log file" msgstr "Arquivo de log" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 +#: src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "Raciocínio por {seconds:.1f}s" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 +#: src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o para expandir)" @@ -2107,97 +2310,113 @@ msgstr "Não, sempre negar \"{rule}\" (esta sessão)" msgid "No, always reject this tool" msgstr "Não, sempre rejeitar esta ferramenta" -#: src/iac_code/ui/repl.py:406 +#: src/iac_code/ui/repl.py:412 msgid "Press Ctrl+C again to exit." msgstr "Pressione Ctrl+C novamente para sair." -#: src/iac_code/ui/repl.py:431 +#: src/iac_code/ui/repl.py:437 msgid "Interrupted." msgstr "Interrompido." -#: src/iac_code/ui/repl.py:468 -msgid "Goodbye!" -msgstr "Até logo!" - -#: src/iac_code/ui/repl.py:469 -msgid "Resume this session with:" -msgstr "Para retomar esta sessão, execute:" - -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:496 msgid "Update now" msgstr "Atualizar agora" -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:498 msgid "Run the shown update command and exit when it succeeds." -msgstr "Executa o comando de atualização mostrado e sai quando ele for concluído com sucesso." +msgstr "" +"Executa o comando de atualização mostrado e sai quando ele for concluído " +"com sucesso." -#: src/iac_code/ui/repl.py:499 +#: src/iac_code/ui/repl.py:501 msgid "Skip" msgstr "Ignorar" -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:503 msgid "Continue with the current version for this session." msgstr "Continuar com a versão atual nesta sessão." -#: src/iac_code/ui/repl.py:504 +#: src/iac_code/ui/repl.py:506 msgid "Skip until next version" msgstr "Ignorar até a próxima versão" -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:508 msgid "Hide this update until a newer version is available." msgstr "Ocultar esta atualização até que uma versão mais nova esteja disponível." -#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 +#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 msgid "Update command failed. Continuing with the current version." msgstr "O comando de atualização falhou. Continuando com a versão atual." -#: src/iac_code/ui/repl.py:530 +#: src/iac_code/ui/repl.py:532 msgid "Update completed. Restart iac-code to continue." msgstr "Atualização concluída. Reinicie o iac-code para continuar." -#: src/iac_code/ui/repl.py:568 +#: src/iac_code/ui/repl.py:570 msgid "No image in clipboard." msgstr "Nenhuma imagem na área de transferência." -#: src/iac_code/ui/repl.py:754 +#: src/iac_code/ui/repl.py:756 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:759 +#: src/iac_code/ui/repl.py:761 msgid "Shell command support is unavailable." msgstr "O suporte a comandos shell não está disponível." -#: src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." -msgstr "Habilidade desconhecida: ${name}. Digite / para listar comandos e habilidades." +msgstr "" +"Habilidade desconhecida: ${name}. Digite / para listar comandos e " +"habilidades." -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:830 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "Comando desconhecido: /{name}. Digite /help para ver os comandos." -#: src/iac_code/ui/repl.py:833 +#: src/iac_code/ui/repl.py:835 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ invoca apenas habilidades. Use /{name} em vez disso." -#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 +#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 #, python-brace-format msgid "Command error: {error}" msgstr "Erro de comando: {error}" -#: src/iac_code/ui/repl.py:862 +#: src/iac_code/ui/repl.py:864 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Comando sem tratador: {name}" -#: src/iac_code/ui/repl.py:1167 +#: src/iac_code/ui/repl.py:1126 +msgid "Goodbye!" +msgstr "Até logo!" + +#: src/iac_code/ui/repl.py:1127 +msgid "Resume this session with:" +msgstr "Para retomar esta sessão, execute:" + +#: src/iac_code/ui/repl.py:1130 +msgid "Session ID" +msgstr "ID da sessão" + +#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sessão não encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1233 +msgid "Session name: " +msgstr "Nome da sessão: " + +#: src/iac_code/ui/repl.py:1239 +msgid "Session name cannot be empty." +msgstr "O nome da sessão não pode estar vazio." + +#: src/iac_code/ui/repl.py:1251 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2208,31 +2427,43 @@ msgstr "" "Para retomar, execute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1287 +#: src/iac_code/ui/repl.py:1257 +msgid "Multiple sessions match. Resume one by ID:" +msgstr "Várias sessões correspondem. Retome uma por ID:" + +#: src/iac_code/ui/repl.py:1370 msgid "This conversation is from a different directory." msgstr "Esta conversa é de outro diretório." -#: src/iac_code/ui/repl.py:1289 +#: src/iac_code/ui/repl.py:1372 msgid "To resume, run:" msgstr "Para retomar, execute:" -#: src/iac_code/ui/repl.py:1294 +#: src/iac_code/ui/repl.py:1377 msgid "(Command copied to clipboard)" msgstr "(Comando copiado para a área de transferência)" -#: src/iac_code/ui/repl.py:1451 +#: src/iac_code/ui/repl.py:1534 #, python-brace-format -msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." -msgstr "O modelo atual {model} não suporta entrada de imagem. Use /model para alternar para um modelo com capacidade de visão." +msgid "" +"Current model {model} does not support image input. Use /model to switch " +"to a vision-capable model." +msgstr "" +"O modelo atual {model} não suporta entrada de imagem. Use /model para " +"alternar para um modelo com capacidade de visão." -#: src/iac_code/ui/repl.py:1460 +#: src/iac_code/ui/repl.py:1543 #, python-brace-format msgid "Image error: {err}" msgstr "Erro de imagem: {err}" -#: src/iac_code/ui/repl.py:1477 -msgid "Failed to persist image to cache; it will only exist in memory for this turn." -msgstr "Falha ao persistir a imagem no cache; ela só existirá na memória durante este turno." +#: src/iac_code/ui/repl.py:1560 +msgid "" +"Failed to persist image to cache; it will only exist in memory for this " +"turn." +msgstr "" +"Falha ao persistir a imagem no cache; ela só existirá na memória durante " +"este turno." #: src/iac_code/ui/spinner.py:52 msgid "Processing" @@ -2322,87 +2553,87 @@ msgstr "Digite para buscar arquivos..." msgid "No matching files" msgstr "Nenhum arquivo correspondente" -#: src/iac_code/ui/dialogs/resume_picker.py:114 +#: src/iac_code/ui/dialogs/resume_picker.py:116 msgid "Search..." msgstr "Buscar..." -#: src/iac_code/ui/dialogs/resume_picker.py:359 +#: src/iac_code/ui/dialogs/resume_picker.py:374 msgid "Resume Session" msgstr "Retomar sessão" -#: src/iac_code/ui/dialogs/resume_picker.py:369 +#: src/iac_code/ui/dialogs/resume_picker.py:384 msgid "No sessions found" msgstr "Nenhuma sessão encontrada" -#: src/iac_code/ui/dialogs/resume_picker.py:427 +#: src/iac_code/ui/dialogs/resume_picker.py:444 msgid "show current dir" msgstr "mostrar diretório atual" -#: src/iac_code/ui/dialogs/resume_picker.py:429 +#: src/iac_code/ui/dialogs/resume_picker.py:446 msgid "show all projects" msgstr "mostrar todos os projetos" -#: src/iac_code/ui/dialogs/resume_picker.py:432 +#: src/iac_code/ui/dialogs/resume_picker.py:449 msgid "show all branches" msgstr "mostrar todos os branches" -#: src/iac_code/ui/dialogs/resume_picker.py:434 +#: src/iac_code/ui/dialogs/resume_picker.py:451 msgid "only show current branch" msgstr "mostrar apenas o branch atual" -#: src/iac_code/ui/dialogs/resume_picker.py:435 +#: src/iac_code/ui/dialogs/resume_picker.py:452 msgid "preview" msgstr "pré-visualização" -#: src/iac_code/ui/dialogs/resume_picker.py:436 +#: src/iac_code/ui/dialogs/resume_picker.py:453 msgid "Type to search" msgstr "Digite para buscar" -#: src/iac_code/ui/dialogs/resume_picker.py:437 +#: src/iac_code/ui/dialogs/resume_picker.py:454 msgid "cancel" msgstr "cancelar" -#: src/iac_code/ui/dialogs/resume_picker.py:552 +#: src/iac_code/ui/dialogs/resume_picker.py:569 #, python-brace-format msgid "{n} more line{s}" msgstr "mais {n} linha{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:566 +#: src/iac_code/ui/dialogs/resume_picker.py:583 #, python-brace-format msgid "{n} message{s}" msgstr "{n} mensagem{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:580 +#: src/iac_code/ui/dialogs/resume_picker.py:597 msgid "resume" msgstr "retomar" -#: src/iac_code/ui/dialogs/resume_picker.py:584 +#: src/iac_code/ui/dialogs/resume_picker.py:601 msgid "back" msgstr "voltar" -#: src/iac_code/ui/dialogs/resume_picker.py:589 +#: src/iac_code/ui/dialogs/resume_picker.py:606 msgid "scroll" msgstr "rolar" -#: src/iac_code/ui/dialogs/resume_picker.py:608 +#: src/iac_code/ui/dialogs/resume_picker.py:625 msgid "(empty session)" msgstr "(sessão vazia)" -#: src/iac_code/ui/dialogs/resume_picker.py:728 +#: src/iac_code/ui/dialogs/resume_picker.py:745 msgid "just now" msgstr "agora mesmo" -#: src/iac_code/ui/dialogs/resume_picker.py:731 +#: src/iac_code/ui/dialogs/resume_picker.py:748 #, python-brace-format msgid "{n} minute{s} ago" msgstr "há {n} minuto{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:734 +#: src/iac_code/ui/dialogs/resume_picker.py:751 #, python-brace-format msgid "{n} hour{s} ago" msgstr "há {n} hora{s}" -#: src/iac_code/ui/dialogs/resume_picker.py:736 +#: src/iac_code/ui/dialogs/resume_picker.py:753 #, python-brace-format msgid "{n} day{s} ago" msgstr "há {n} dia{s}" @@ -2422,8 +2653,12 @@ msgstr "{current} de {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" -msgstr "{count} habilidades - Espaço para alternar, Enter para salvar, Tab para ordenar, Esc para cancelar" +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" +msgstr "" +"{count} habilidades - Espaço para alternar, Enter para salvar, Tab para " +"ordenar, Esc para cancelar" #: src/iac_code/ui/dialogs/skills_picker.py:171 #, python-brace-format @@ -2509,8 +2744,12 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code no Windows requer o Git for Windows." #: src/iac_code/utils/platform.py:41 -msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." -msgstr "Se estiver instalado mas não estiver no PATH, defina a variável de ambiente IAC_CODE_GIT_BASH_PATH." +msgid "" +"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " +"variable." +msgstr "" +"Se estiver instalado mas não estiver no PATH, defina a variável de " +"ambiente IAC_CODE_GIT_BASH_PATH." #: src/iac_code/utils/platform.py:44 msgid "To install:" @@ -2521,8 +2760,12 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " Opção 1 - winget (requer acesso a github.com):" #: src/iac_code/utils/platform.py:52 -msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" -msgstr " Opção 2 - se você não consegue acessar github.com, execute isto para instalar via npmmirror:" +msgid "" +" Option 2 - if you cannot reach github.com, run this to install via " +"npmmirror:" +msgstr "" +" Opção 2 - se você não consegue acessar github.com, execute isto para " +"instalar via npmmirror:" #~ msgid "DashScope Token Plan" #~ msgstr "DashScope Token Plan" @@ -2530,8 +2773,14 @@ msgstr " Opção 2 - se você não consegue acessar github.com, execute isto pa #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter not available" -#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." -#~ msgstr "O provedor LLM está bloqueado por '{source}'. Para alterar, modifique llm_source em settings.yml." +#~ msgid "" +#~ "LLM provider is locked by '{source}'." +#~ " To change, modify llm_source in " +#~ "settings.yml." +#~ msgstr "" +#~ "O provedor LLM está bloqueado por " +#~ "'{source}'. Para alterar, modifique llm_source" +#~ " em settings.yml." #~ msgid "Provider switched: {status}" #~ msgstr "Provider alterado: {status}" @@ -2548,6 +2797,16 @@ msgstr " Opção 2 - se você não consegue acessar github.com, execute isto pa #~ msgid "Cache create" #~ msgstr "Criação de cache" -#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" -#~ msgstr "{count} habilidades - Espaço para alternar, Enter para salvar, / para buscar, t para ordenar, Esc para cancelar" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" +#~ msgstr "" +#~ "{count} habilidades - Espaço para " +#~ "alternar, Enter para salvar, / para " +#~ "buscar, t para ordenar, Esc para " +#~ "cancelar" + +#~ msgid "Resume a session by ID" +#~ msgstr "Retomar uma sessão pelo ID" 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 9826a09..665de49 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 21:26+0800\n" +"POT-Creation-Date: 2026-06-02 22:08+0800\n" "PO-Revision-Date: 2026-04-02 00:00+0000\n" "Last-Translator: \n" "Language: zh\n" @@ -23,79 +23,120 @@ msgid "Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): { msgstr "无效的 IAC_CODE_PROVIDER 值:{!r}。有效值(不区分大小写):{}" #: src/iac_code/a2a/transports/base.py:175 -msgid "Unix domain socket transport is not supported on Windows. Use --transport http or --transport stdio instead." +msgid "" +"Unix domain socket transport is not supported on Windows. Use --transport" +" http or --transport stdio instead." msgstr "Windows 不支持 Unix 域套接字传输。请使用 --transport http 或 --transport stdio。" -#: src/iac_code/acp/slash_registry.py:44 +#: src/iac_code/acp/server.py:413 src/iac_code/acp/server.py:429 +#: src/iac_code/acp/server.py:456 +msgid "Session not found" +msgstr "未找到会话" + +#: src/iac_code/acp/server.py:416 +#, python-brace-format +msgid "Session name is ambiguous. Candidates: {candidates}" +msgstr "会话名称不唯一。候选项:{candidates}" + +#: src/iac_code/acp/server.py:434 src/iac_code/acp/server.py:708 #, python-brace-format -msgid "Command '/{cmd_name}' is not supported over ACP. Supported commands: {supported}" +msgid "Session belongs to another project. Run: {hint}" +msgstr "会话属于另一个项目。请运行:{hint}" + +#: src/iac_code/acp/slash_registry.py:46 +#, python-brace-format +msgid "" +"Command '/{cmd_name}' is not supported over ACP. Supported commands: " +"{supported}" msgstr "命令 '/{cmd_name}' 不支持通过 ACP 使用。支持的命令:{supported}" -#: src/iac_code/acp/slash_registry.py:58 +#: src/iac_code/acp/slash_registry.py:62 #, python-brace-format msgid "Command '/{cmd_name}' handler not implemented." msgstr "命令 '/{cmd_name}' 的处理器尚未实现。" -#: src/iac_code/acp/slash_registry.py:70 +#: src/iac_code/acp/slash_registry.py:74 #, python-brace-format msgid "Compaction failed: {error}" msgstr "压缩失败:{error}" -#: src/iac_code/acp/slash_registry.py:73 src/iac_code/commands/compact.py:24 +#: src/iac_code/acp/slash_registry.py:77 src/iac_code/commands/compact.py:24 msgid "Nothing to compact: conversation is empty." msgstr "无内容可压缩:对话为空。" -#: src/iac_code/acp/slash_registry.py:76 src/iac_code/commands/compact.py:27 +#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:27 #, python-brace-format -msgid "Conversation too short to compact: all messages are within the recent {turns}-turn preservation window." +msgid "" +"Conversation too short to compact: all messages are within the recent " +"{turns}-turn preservation window." msgstr "对话过短,无需压缩:所有消息都在最近 {turns} 轮保留窗口内。" -#: src/iac_code/acp/slash_registry.py:80 src/iac_code/commands/compact.py:30 +#: src/iac_code/acp/slash_registry.py:84 src/iac_code/commands/compact.py:30 msgid "Compaction failed. See logs for details." msgstr "压缩失败,详情请查看日志。" -#: src/iac_code/acp/slash_registry.py:85 +#: src/iac_code/acp/slash_registry.py:89 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent} reduction). Context usage: {usage}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent} reduction)." +" Context usage: {usage}" msgstr "上下文已压缩:{original} → {compacted} tokens(减少 {percent})。上下文用量:{usage}" -#: src/iac_code/acp/slash_registry.py:99 +#: src/iac_code/acp/slash_registry.py:103 #, python-brace-format msgid "Clear failed: {error}" msgstr "清除失败:{error}" -#: src/iac_code/acp/slash_registry.py:100 +#: src/iac_code/acp/slash_registry.py:104 msgid "Conversation history cleared." msgstr "对话历史已清除。" -#: src/iac_code/acp/slash_registry.py:116 src/iac_code/commands/debug.py:34 +#: src/iac_code/acp/slash_registry.py:120 src/iac_code/commands/debug.py:34 #, python-brace-format msgid "Debug logging is on. Log file: {path}" msgstr "调试日志已启用。日志文件:{path}" -#: src/iac_code/acp/slash_registry.py:117 src/iac_code/commands/debug.py:35 +#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:35 msgid "Debug logging is off." msgstr "调试日志已关闭。" -#: src/iac_code/acp/slash_registry.py:121 src/iac_code/commands/debug.py:39 +#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:39 #, python-brace-format msgid "Debug logging enabled. Log file: {path}" msgstr "调试日志已启用。日志文件:{path}" -#: src/iac_code/acp/slash_registry.py:125 src/iac_code/commands/debug.py:43 +#: src/iac_code/acp/slash_registry.py:129 src/iac_code/commands/debug.py:43 msgid "Debug logging disabled." msgstr "调试日志已关闭。" -#: src/iac_code/acp/slash_registry.py:127 src/iac_code/commands/debug.py:45 +#: src/iac_code/acp/slash_registry.py:131 src/iac_code/commands/debug.py:45 msgid "Usage: /debug [on|off]" msgstr "用法:/debug [on|off]" -#: src/iac_code/acp/slash_registry.py:132 src/iac_code/commands/memory.py:84 +#: src/iac_code/acp/slash_registry.py:136 src/iac_code/commands/memory.py:84 msgid "Memory manager is unavailable." msgstr "记忆管理器不可用。" -#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 src/iac_code/ui/repl.py:793 -#: src/iac_code/ui/repl.py:807 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +msgid "Usage: /rename " +msgstr "用法:/rename <名称>" + +#: src/iac_code/acp/slash_registry.py:152 +msgid "Rename is only available after a session is created." +msgstr "创建会话后才能重命名。" + +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#, python-brace-format +msgid "Session is already named {name}" +msgstr "会话已命名为 {name}" + +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#, python-brace-format +msgid "Renamed session to {name}" +msgstr "已将会话重命名为 {name}" + +#: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 +#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 msgid "Permission denied." msgstr "权限被拒绝。" @@ -112,7 +153,8 @@ msgstr "探索" msgid "Plan" msgstr "规划" -#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 src/iac_code/agent/agent_tool.py:280 +#: src/iac_code/agent/agent_tool.py:278 src/iac_code/agent/agent_tool.py:279 +#: src/iac_code/agent/agent_tool.py:280 msgid "Agent" msgstr "智能体" @@ -164,14 +206,16 @@ msgid "" "\n" " Fix: run iac-code then type /auth\n" " or: set IAC_CODE_API_KEY=\n" -" Docs: https://aliyun.github.io/iac-code/docs/configuration/authentication\n" +" Docs: https://aliyun.github.io/iac-" +"code/docs/configuration/authentication\n" msgstr "" "\n" " {error}\n" "\n" " 修复方法:运行 iac-code 后输入 /auth\n" " 或者设置环境变量:IAC_CODE_API_KEY=<你的密钥>\n" -" 文档:https://aliyun.github.io/iac-code/zh-Hans/docs/configuration/authentication\n" +" 文档:https://aliyun.github.io/iac-code/zh-" +"Hans/docs/configuration/authentication\n" #: src/iac_code/cli/install_git_bash.py:40 #, python-brace-format @@ -193,8 +237,8 @@ msgstr "安装失败(PowerShell 退出码 {})" #: src/iac_code/cli/install_git_bash.py:77 msgid "" -"Installer exited but bash.exe was not found in common locations; UAC may have been cancelled or the installer used a " -"non-standard path." +"Installer exited but bash.exe was not found in common locations; UAC may " +"have been cancelled or the installer used a non-standard path." msgstr "安装程序已退出,但常见路径下未找到 bash.exe;可能 UAC 被取消或安装程序使用了非标准路径。" #: src/iac_code/cli/install_git_bash.py:84 @@ -218,8 +262,11 @@ msgstr "通过 npmmirror 镜像安装 Git for Windows(仅 Windows)。" msgid "YAML config file containing A2A client options" msgstr "包含 A2A 客户端选项的 YAML 配置文件" -#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 -msgid "A2A client dependencies are missing. Install with: pip install 'iac-code[a2a]'" +#: src/iac_code/cli/main.py:68 src/iac_code/cli/main.py:1501 +#: src/iac_code/cli/main.py:1862 src/iac_code/cli/main.py:1901 +msgid "" +"A2A client dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" msgstr "缺少 A2A 客户端依赖。请使用以下命令安装:pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:82 @@ -238,7 +285,8 @@ msgstr "输出格式:text、json、stream-json" msgid "Maximum agent turns in headless mode" msgstr "无头模式下最大智能体轮次" -#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 src/iac_code/cli/main.py:591 +#: src/iac_code/cli/main.py:86 src/iac_code/cli/main.py:366 +#: src/iac_code/cli/main.py:591 msgid "Enable debug logging" msgstr "启用调试日志" @@ -251,8 +299,8 @@ msgid "Show version and exit" msgstr "显示版本号并退出" #: src/iac_code/cli/main.py:89 -msgid "Resume a session by ID" -msgstr "通过 ID 恢复会话" +msgid "Resume a session by ID or name" +msgstr "通过 ID 或名称恢复会话" #: src/iac_code/cli/main.py:90 msgid "Resume the most recent session" @@ -263,11 +311,15 @@ msgid "Install completion for the current shell." msgstr "为当前 shell 安装自动补全。" #: src/iac_code/cli/main.py:105 src/iac_code/i18n/__init__.py:56 -msgid "Show completion for the current shell, to copy it or customize the installation." +msgid "" +"Show completion for the current shell, to copy it or customize the " +"installation." msgstr "显示当前 shell 的自动补全脚本,可复制或自定义安装。" #: src/iac_code/cli/main.py:110 -msgid "Comma-separated tool permission patterns to allow, e.g. 'bash(git *),write_file'" +msgid "" +"Comma-separated tool permission patterns to allow, e.g. 'bash(git " +"*),write_file'" msgstr "允许的工具权限模式(逗号分隔),例如 'bash(git *),write_file'" #: src/iac_code/cli/main.py:115 @@ -312,27 +364,37 @@ msgid "YAML config file for A2A server options" msgstr "用于 A2A 服务器选项的 YAML 配置文件" #: src/iac_code/cli/main.py:584 -msgid "HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, not a registered A2A port." +msgid "" +"HTTP server port. 41242 is the iac-code default inspired by Gemini CLI, " +"not a registered A2A port." msgstr "HTTP 服务器端口。41242 是受 Gemini CLI 启发的 iac-code 默认值,并非已注册的 A2A 端口。" #: src/iac_code/cli/main.py:589 -msgid "A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or redis-streams" +msgid "" +"A2A transport: http, stdio, unix, websocket, grpc, grpc-jsonrpc, or " +"redis-streams" msgstr "A2A 传输方式:http、stdio、unix、websocket、grpc、grpc-jsonrpc 或 redis-streams" #: src/iac_code/cli/main.py:595 -msgid "Expose A2A thinking signal types; repeat for multiple. Values: raw-thinking, tool-trace." +msgid "" +"Expose A2A thinking signal types; repeat for multiple. Values: raw-" +"thinking, tool-trace." msgstr "暴露 A2A thinking 信号类型;可重复指定多个。取值:raw-thinking、tool-trace。" #: src/iac_code/cli/main.py:646 -msgid "A2A server dependencies are missing. Install with: pip install 'iac-code[a2a]'" +msgid "" +"A2A server dependencies are missing. Install with: pip install 'iac-" +"code[a2a]'" msgstr "缺少 A2A 服务器依赖。请使用以下命令安装:pip install 'iac-code[a2a]'" #: src/iac_code/cli/main.py:778 msgid "Send a prompt to an A2A JSON-RPC endpoint." msgstr "向 A2A JSON-RPC 端点发送提示。" -#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 -#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 +#: src/iac_code/cli/main.py:781 src/iac_code/cli/main.py:951 +#: src/iac_code/cli/main.py:1004 src/iac_code/cli/main.py:1064 +#: src/iac_code/cli/main.py:1114 src/iac_code/cli/main.py:1163 +#: src/iac_code/cli/main.py:1242 src/iac_code/cli/main.py:1299 #: src/iac_code/cli/main.py:1355 src/iac_code/cli/main.py:1412 msgid "A2A JSON-RPC endpoint URL" msgstr "A2A JSON-RPC 端点 URL" @@ -357,33 +419,48 @@ msgstr "随请求一起发送的工作目录元数据" msgid "A2A context ID to continue" msgstr "要继续的 A2A 上下文 ID" -#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 -#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 -#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 src/iac_code/cli/main.py:1413 +#: src/iac_code/cli/main.py:787 src/iac_code/cli/main.py:882 +#: src/iac_code/cli/main.py:954 src/iac_code/cli/main.py:1011 +#: src/iac_code/cli/main.py:1066 src/iac_code/cli/main.py:1116 +#: src/iac_code/cli/main.py:1170 src/iac_code/cli/main.py:1245 +#: src/iac_code/cli/main.py:1303 src/iac_code/cli/main.py:1358 +#: src/iac_code/cli/main.py:1413 msgid "Bearer token for A2A HTTP requests" msgstr "用于 A2A HTTP 请求的 Bearer 令牌" -#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 -#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 -#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 src/iac_code/cli/main.py:1414 +#: src/iac_code/cli/main.py:788 src/iac_code/cli/main.py:883 +#: src/iac_code/cli/main.py:955 src/iac_code/cli/main.py:1012 +#: src/iac_code/cli/main.py:1067 src/iac_code/cli/main.py:1117 +#: src/iac_code/cli/main.py:1171 src/iac_code/cli/main.py:1246 +#: src/iac_code/cli/main.py:1304 src/iac_code/cli/main.py:1359 +#: src/iac_code/cli/main.py:1414 msgid "Basic auth username for A2A HTTP requests" msgstr "用于 A2A HTTP 请求的基本认证用户名" -#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 -#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 -#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 src/iac_code/cli/main.py:1415 +#: src/iac_code/cli/main.py:789 src/iac_code/cli/main.py:884 +#: src/iac_code/cli/main.py:956 src/iac_code/cli/main.py:1013 +#: src/iac_code/cli/main.py:1068 src/iac_code/cli/main.py:1118 +#: src/iac_code/cli/main.py:1172 src/iac_code/cli/main.py:1247 +#: src/iac_code/cli/main.py:1305 src/iac_code/cli/main.py:1360 +#: src/iac_code/cli/main.py:1415 msgid "Basic auth password for A2A HTTP requests" msgstr "用于 A2A HTTP 请求的基本认证密码" -#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 -#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 -#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 src/iac_code/cli/main.py:1416 +#: src/iac_code/cli/main.py:790 src/iac_code/cli/main.py:885 +#: src/iac_code/cli/main.py:957 src/iac_code/cli/main.py:1014 +#: src/iac_code/cli/main.py:1069 src/iac_code/cli/main.py:1119 +#: src/iac_code/cli/main.py:1173 src/iac_code/cli/main.py:1248 +#: src/iac_code/cli/main.py:1306 src/iac_code/cli/main.py:1361 +#: src/iac_code/cli/main.py:1416 msgid "API key for A2A HTTP requests" msgstr "用于 A2A HTTP 请求的 API 密钥" -#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 -#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 -#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 src/iac_code/cli/main.py:1417 +#: src/iac_code/cli/main.py:791 src/iac_code/cli/main.py:886 +#: src/iac_code/cli/main.py:958 src/iac_code/cli/main.py:1015 +#: src/iac_code/cli/main.py:1070 src/iac_code/cli/main.py:1120 +#: src/iac_code/cli/main.py:1174 src/iac_code/cli/main.py:1249 +#: src/iac_code/cli/main.py:1307 src/iac_code/cli/main.py:1362 +#: src/iac_code/cli/main.py:1417 msgid "HTTP header name for A2A API key" msgstr "A2A API 密钥使用的 HTTP 请求头名称" @@ -419,8 +496,10 @@ msgstr "A2A 代理基础 URL" msgid "Get an A2A task." msgstr "获取 A2A 任务。" -#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 -#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 src/iac_code/cli/main.py:1356 +#: src/iac_code/cli/main.py:952 src/iac_code/cli/main.py:1065 +#: src/iac_code/cli/main.py:1115 src/iac_code/cli/main.py:1164 +#: src/iac_code/cli/main.py:1243 src/iac_code/cli/main.py:1300 +#: src/iac_code/cli/main.py:1356 msgid "A2A task ID" msgstr "A2A 任务 ID" @@ -468,7 +547,8 @@ msgstr "订阅 A2A 任务事件流。" msgid "Create an A2A task push notification config." msgstr "创建 A2A 任务推送通知配置。" -#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 src/iac_code/cli/main.py:1357 +#: src/iac_code/cli/main.py:1165 src/iac_code/cli/main.py:1244 +#: src/iac_code/cli/main.py:1357 msgid "Push config ID" msgstr "推送配置 ID" @@ -532,81 +612,89 @@ msgstr "持久化 A2A 路由的目录" msgid "Save the provided routes as a route snapshot" msgstr "将提供的路由保存为路由快照" -#: src/iac_code/commands/__init__.py:25 +#: src/iac_code/commands/__init__.py:26 msgid "Show available commands" msgstr "显示可用命令" -#: src/iac_code/commands/__init__.py:34 +#: src/iac_code/commands/__init__.py:35 msgid "Clear conversation history" msgstr "清除对话历史" -#: src/iac_code/commands/__init__.py:42 +#: src/iac_code/commands/__init__.py:43 msgid "Show or switch model" msgstr "显示或切换模型" -#: src/iac_code/commands/__init__.py:51 +#: src/iac_code/commands/__init__.py:52 msgid "Show or switch thinking effort" msgstr "显示或切换思考强度" -#: src/iac_code/commands/__init__.py:60 +#: src/iac_code/commands/__init__.py:61 msgid "Compact conversation context" msgstr "压缩对话上下文" -#: src/iac_code/commands/__init__.py:62 +#: src/iac_code/commands/__init__.py:63 msgid "Compacting conversation" msgstr "正在压缩对话" -#: src/iac_code/commands/__init__.py:69 +#: src/iac_code/commands/__init__.py:70 msgid "Exit the application" msgstr "退出应用程序" -#: src/iac_code/commands/__init__.py:78 +#: src/iac_code/commands/__init__.py:79 msgid "Authenticate with LLM provider" msgstr "配置 LLM 提供商认证" -#: src/iac_code/commands/__init__.py:87 +#: src/iac_code/commands/__init__.py:88 msgid "Toggle debug logging" msgstr "切换调试日志开关" -#: src/iac_code/commands/__init__.py:96 +#: src/iac_code/commands/__init__.py:97 msgid "View and manage persistent memories" msgstr "查看和管理持久记忆" -#: src/iac_code/commands/__init__.py:98 +#: src/iac_code/commands/__init__.py:99 msgid "[|search |delete |help]" msgstr "[<名称>|search <查询>|delete <名称>|help]" -#: src/iac_code/commands/__init__.py:105 +#: src/iac_code/commands/__init__.py:106 msgid "Resume a previous session" msgstr "恢复之前的会话" -#: src/iac_code/commands/__init__.py:107 +#: src/iac_code/commands/__init__.py:108 msgid "[conversation id or search term]" msgstr "[会话 ID 或搜索词]" -#: src/iac_code/commands/__init__.py:114 +#: src/iac_code/commands/__init__.py:115 +msgid "Rename the current session" +msgstr "重命名当前会话" + +#: src/iac_code/commands/__init__.py:124 msgid "Manage skills" msgstr "管理技能" -#: src/iac_code/commands/__init__.py:122 +#: src/iac_code/commands/__init__.py:132 msgid "Show current session status" msgstr "显示当前会话状态" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "导航" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 -#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1549 -#: src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 +#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 +#: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "确认" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 src/iac_code/commands/auth.py:538 -#: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 -#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1441 src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:536 +#: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 +#: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 +#: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 +#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1549 msgid "Back" msgstr "返回" @@ -618,8 +706,9 @@ msgstr "保留" msgid "Re-enter" msgstr "重新输入" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 src/iac_code/commands/auth.py:919 -#: src/iac_code/commands/auth.py:927 src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 +#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 +#: src/iac_code/commands/auth.py:954 msgid " (current)" msgstr " (当前)" @@ -656,17 +745,20 @@ msgstr "配置 IaC 云服务" msgid "Select configuration type" msgstr "选择配置类型" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 src/iac_code/commands/auth.py:995 -#: src/iac_code/commands/auth.py:1074 src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 +#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 +#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 msgid "Auth cancelled" msgstr "认证已取消" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 +#: src/iac_code/commands/auth.py:1049 #, python-brace-format msgid "Select provider — {group}" msgstr "选择提供商 — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 +#: src/iac_code/commands/auth.py:1034 msgid "Third-party" msgstr "第三方" @@ -762,8 +854,9 @@ msgstr "选择云服务商" msgid "Credential" msgstr "凭证" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 src/iac_code/commands/auth.py:1525 -#: src/iac_code/commands/status.py:36 src/iac_code/ui/renderer.py:455 +#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 +#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "地域" @@ -898,8 +991,12 @@ msgstr "没有活动的智能体循环。" #: src/iac_code/commands/compact.py:35 #, python-brace-format -msgid "Context compacted: {original} → {compacted} tokens ({percent_display} reduction). Context usage: {usage_display}" -msgstr "上下文已压缩:{original} → {compacted} tokens(减少 {percent_display})。上下文用量:{usage_display}" +msgid "" +"Context compacted: {original} → {compacted} tokens ({percent_display} " +"reduction). Context usage: {usage_display}" +msgstr "" +"上下文已压缩:{original} → {compacted} tokens(减少 " +"{percent_display})。上下文用量:{usage_display}" #: src/iac_code/commands/debug.py:21 msgid "Debug command requires a context." @@ -909,7 +1006,8 @@ msgstr "调试命令需要上下文。" msgid "No active session." msgstr "没有活动的会话。" -#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 src/iac_code/commands/model.py:99 +#: src/iac_code/commands/effort.py:54 src/iac_code/commands/model.py:95 +#: src/iac_code/commands/model.py:99 msgid "No configured providers. Run /auth first." msgstr "没有已配置的提供商。请先运行 /auth。" @@ -1003,7 +1101,9 @@ msgstr "记忆 '{name}' 已删除。" #: src/iac_code/commands/model.py:57 #, python-brace-format -msgid "Model is managed by '{source}'. To change model, modify it in {source} or switch provider via /auth." +msgid "" +"Model is managed by '{source}'. To change model, modify it in {source} or" +" switch provider via /auth." msgstr "模型由 '{source}' 管理。如需修改模型,请在 {source} 中调整,或通过 /auth 切换其他 Provider。" #: src/iac_code/commands/model.py:88 src/iac_code/commands/model.py:143 @@ -1021,23 +1121,36 @@ msgstr "当前模型:{model}" msgid "Kept model as {model}" msgstr "保持模型为 {model}" -#: src/iac_code/commands/resume.py:21 +#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +msgid "Rename is only available in interactive mode." +msgstr "重命名仅在交互模式下可用。" + +#: src/iac_code/commands/rename.py:30 +msgid "Rename cancelled" +msgstr "已取消重命名" + +#: src/iac_code/commands/resume.py:22 msgid "Resume is only available in interactive mode." msgstr "/resume 仅在交互模式下可用。" -#: src/iac_code/commands/resume.py:27 +#: src/iac_code/commands/resume.py:28 msgid "Resume is unavailable: session index not initialised." msgstr "无法恢复:会话索引未初始化。" -#: src/iac_code/commands/resume.py:32 +#: src/iac_code/commands/resume.py:33 src/iac_code/commands/resume.py:36 #, python-brace-format msgid "Session not found: {arg}" msgstr "未找到会话:{arg}" -#: src/iac_code/commands/resume.py:47 +#: src/iac_code/commands/resume.py:52 src/iac_code/commands/resume.py:68 msgid "Resume cancelled" msgstr "已取消恢复" +#: src/iac_code/commands/resume.py:55 +#, python-brace-format +msgid "Unable to resolve session: {arg}" +msgstr "无法解析会话:{arg}" + #: src/iac_code/commands/skills.py:14 msgid "Skills management is only available in interactive mode." msgstr "技能管理仅在交互模式下可用。" @@ -1062,7 +1175,8 @@ msgstr "status 命令需要 REPL 上下文。" msgid "Status is only available in interactive mode." msgstr "status 仅在交互模式下可用。" -#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:138 +#: src/iac_code/commands/status.py:33 src/iac_code/ui/banner.py:136 +#: src/iac_code/ui/banner.py:138 msgid "Session" msgstr "会话" @@ -1070,7 +1184,8 @@ msgstr "会话" msgid "Provider" msgstr "提供商" -#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/status.py:34 src/iac_code/commands/status.py:35 +#: src/iac_code/commands/status.py:36 msgid "not configured" msgstr "未配置" @@ -1179,22 +1294,30 @@ msgstr "未知的 Provider 标识:'{key}'。请运行 /auth 进行配置。" #: src/iac_code/providers/manager.py:100 #, python-brace-format -msgid "No API key configured for provider '{provider}' (model: {model}). Run /auth to configure." +msgid "" +"No API key configured for provider '{provider}' (model: {model}). Run " +"/auth to configure." msgstr "提供商 '{provider}' 未配置 API 密钥(模型: {model})。请运行 /auth 进行配置。" #: src/iac_code/providers/openai_provider.py:307 #, python-brace-format msgid "" -"API returned no data. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-compatible " -"endpoints require a /v1 suffix (e.g. {base_url}/v1)." -msgstr "API 未返回数据。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 后缀(如 {base_url}/v1)。" +"API returned no data. Please check that your API Base URL is correct " +"(current: {base_url}). Many OpenAI-compatible endpoints require a /v1 " +"suffix (e.g. {base_url}/v1)." +msgstr "" +"API 未返回数据。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 " +"后缀(如 {base_url}/v1)。" #: src/iac_code/providers/openai_provider.py:348 #, python-brace-format msgid "" -"API returned an invalid response. Please check that your API Base URL is correct (current: {base_url}). Many OpenAI-" -"compatible endpoints require a /v1 suffix (e.g. {base_url}/v1)." -msgstr "API 返回了无效响应。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 后缀(如 {base_url}/v1)。" +"API returned an invalid response. Please check that your API Base URL is " +"correct (current: {base_url}). Many OpenAI-compatible endpoints require a" +" /v1 suffix (e.g. {base_url}/v1)." +msgstr "" +"API 返回了无效响应。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 " +"后缀(如 {base_url}/v1)。" #: src/iac_code/providers/registry.py:415 msgid "Alibaba Cloud Bailian" @@ -1275,21 +1398,34 @@ msgstr "Anthropic 兼容" #: src/iac_code/services/qwenpaw_source.py:205 #, python-brace-format msgid "" -"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not support this provider.\n" +"[QwenPaw mode] Unknown provider '{provider_id}'. iac-code does not " +"support this provider.\n" "Supported QwenPaw provider IDs: {supported_ids}\n" -"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw mode (remove 'llm_source: qwenpaw' from " -"settings.yml)." +"To fix: switch to a supported provider in QwenPaw, or disable QwenPaw " +"mode (remove 'llm_source: qwenpaw' from settings.yml)." msgstr "" "[QwenPaw 模式] 无法识别 provider '{provider_id}',iac-code 不支持该 provider。\n" "支持的 QwenPaw provider ID:{supported_ids}\n" -"解决方法:在 QwenPaw 中切换到已支持的 provider,或关闭 QwenPaw 模式(从 settings.yml 移除 'llm_source: qwenpaw')。" +"解决方法:在 QwenPaw 中切换到已支持的 provider,或关闭 QwenPaw 模式(从 settings.yml 移除 " +"'llm_source: qwenpaw')。" + +#: src/iac_code/services/session_metadata.py:52 +#, python-brace-format +msgid "Session name must match {pattern}" +msgstr "会话名称必须匹配 {pattern}" + +#: src/iac_code/services/session_storage.py:241 +#, python-brace-format +msgid "Session name already exists in this project: {name}" +msgstr "此项目中已存在会话名称:{name}" #: src/iac_code/services/permissions/loader.py:50 #, python-brace-format msgid "Invalid --permission-mode {!r}. Valid values: {}" msgstr "无效的 --permission-mode {!r}。有效值:{}" -#: src/iac_code/services/permissions/pipeline.py:54 src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 +#: src/iac_code/services/permissions/pipeline.py:54 +#: src/iac_code/tools/base.py:199 src/iac_code/tools/bash/bash_tool.py:175 #, python-brace-format msgid "Allow {}?" msgstr "允许 {}?" @@ -1357,21 +1493,28 @@ msgid "Waiting for browser authorization" msgstr "等待浏览器授权" #: src/iac_code/services/providers/aliyun_oauth.py:300 -msgid "1. The browser may show official-cli; this is the Alibaba Cloud official CLI OAuth application." +msgid "" +"1. The browser may show official-cli; this is the Alibaba Cloud official " +"CLI OAuth application." msgstr "1. 浏览器可能显示 official-cli,这是 Alibaba Cloud 官方 CLI OAuth 应用。" #: src/iac_code/services/providers/aliyun_oauth.py:302 -msgid "2. If assignment is required, assign the RAM user or RAM role that is signed in. User groups are not supported." +msgid "" +"2. If assignment is required, assign the RAM user or RAM role that is " +"signed in. User groups are not supported." msgstr "2. 如需分配应用,请分配给当前登录的 RAM 用户或 RAM 角色;不支持用户组。" #: src/iac_code/services/providers/aliyun_oauth.py:306 msgid "" -"3. After assignment, close the old authorization page and run OAuth Login (Browser) again. If it still fails, sign " -"out of Alibaba Cloud and sign in again." +"3. After assignment, close the old authorization page and run OAuth Login" +" (Browser) again. If it still fails, sign out of Alibaba Cloud and sign " +"in again." msgstr "3. 分配后关闭旧授权页,并再次运行 OAuth 登录(浏览器)。若仍失败,请退出 Alibaba Cloud 后重新登录。" #: src/iac_code/services/providers/aliyun_oauth.py:310 -msgid "4. STS credentials refresh when possible until Alibaba Cloud expires them. If refresh fails, run /auth again." +msgid "" +"4. STS credentials refresh when possible until Alibaba Cloud expires " +"them. If refresh fails, run /auth again." msgstr "4. STS 凭证会在可能时自动刷新;如果刷新失败,请重新运行 /auth。" #: src/iac_code/services/providers/aliyun_oauth.py:313 @@ -1380,14 +1523,17 @@ msgstr "按 Esc 可取消等待。" #: src/iac_code/services/providers/aliyun_oauth.py:321 msgid "" -"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to assign the official-cli application, assign it to" -" the exact RAM user or RAM role currently signed in. User groups are not supported. Then close the old authorization " -"page, sign out of Alibaba Cloud and sign in again if needed, and run /auth to choose OAuth Login (Browser) again." +"Timed out waiting for OAuth callback. If Alibaba Cloud asked you to " +"assign the official-cli application, assign it to the exact RAM user or " +"RAM role currently signed in. User groups are not supported. Then close " +"the old authorization page, sign out of Alibaba Cloud and sign in again " +"if needed, and run /auth to choose OAuth Login (Browser) again." msgstr "" -"等待 OAuth 回调超时。如果 Alibaba Cloud 要求分配 official-cli 应用,请将它分配给当前实际登录的 RAM 用户或 RAM 角色。不支持用户组。然后关闭旧的授权页面,必要时退出 Alibaba " -"Cloud 并重新登录,再运行 /auth 重新选择 OAuth 登录(浏览器)。" +"等待 OAuth 回调超时。如果 Alibaba Cloud 要求分配 official-cli 应用,请将它分配给当前实际登录的 RAM 用户或" +" RAM 角色。不支持用户组。然后关闭旧的授权页面,必要时退出 Alibaba Cloud 并重新登录,再运行 /auth 重新选择 OAuth " +"登录(浏览器)。" -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:823 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "技能“{name}”已禁用。运行 /skills 以启用它。" @@ -1407,7 +1553,9 @@ msgid "Skill disabled: {name}" msgstr "技能已禁用:{name}" #: src/iac_code/skills/bundled/simplify.py:25 -msgid "Review changed code for reuse, quality, and efficiency, then fix issues found." +msgid "" +"Review changed code for reuse, quality, and efficiency, then fix issues " +"found." msgstr "审查变更的代码的可复用性、质量和效率,然后修复发现的问题。" #: src/iac_code/tools/edit_file.py:116 @@ -1614,7 +1762,8 @@ msgstr "匹配到拒绝规则:{}" msgid "matched ask rule(s): {}" msgstr "匹配到询问规则:{}" -#: src/iac_code/tools/bash/permissions.py:154 src/iac_code/tools/bash/permissions.py:220 +#: src/iac_code/tools/bash/permissions.py:154 +#: src/iac_code/tools/bash/permissions.py:220 #, python-brace-format msgid "matched allow rule(s): {}" msgstr "匹配到允许规则:{}" @@ -1671,7 +1820,8 @@ msgstr "云API" msgid "Calling {action}..." msgstr "正在调用 {action}..." -#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 src/iac_code/tools/cloud/base_api.py:123 +#: src/iac_code/tools/cloud/aliyun/aliyun_api.py:400 +#: src/iac_code/tools/cloud/base_api.py:123 msgid "Call succeeded" msgstr "调用成功" @@ -1684,7 +1834,8 @@ msgstr "收到响应({count} 行)" msgid "CloudStack" msgstr "云资源栈" -#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 src/iac_code/tools/cloud/base_stack.py:150 +#: src/iac_code/tools/cloud/aliyun/ros_stack_instances.py:117 +#: src/iac_code/tools/cloud/base_stack.py:150 #, python-brace-format msgid "Running {action}..." msgstr "正在运行 {action}..." @@ -1922,11 +2073,15 @@ msgstr "模板 JSON 语法错误(第 {line} 行,第 {col} 列):{msg}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:85 #, python-brace-format -msgid "Template {fmt} parse result is not an object (dict), please check the template format" +msgid "" +"Template {fmt} parse result is not an object (dict), please check the " +"template format" msgstr "模板 {fmt} 解析结果不是对象(dict),请检查模板格式" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:104 -msgid "Template is missing ROSTemplateFormatVersion (ROS templates must include this field, e.g. '2015-09-01')" +msgid "" +"Template is missing ROSTemplateFormatVersion (ROS templates must include " +"this field, e.g. '2015-09-01')" msgstr "模板缺少 ROSTemplateFormatVersion 字段(ROS 模板必须包含此字段,如 '2015-09-01')" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:110 @@ -1940,7 +2095,9 @@ msgstr "Resources 必须是对象(dict),当前类型为 {}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:117 #, python-brace-format -msgid "Resource '{name}' definition must be an object (dict), current type is {type}" +msgid "" +"Resource '{name}' definition must be an object (dict), current type is " +"{type}" msgstr "资源 '{name}' 定义必须是对象(dict),当前类型为 {type}" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:123 @@ -1954,7 +2111,9 @@ msgid "Resource '{name}' has incorrect type '{wrong}', should be '{correct}'" msgstr "资源 '{name}' 的类型 '{wrong}' 不正确,应改为 '{correct}'" #: src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py:151 -msgid "Template structure validation found the following issues, please fix and retry:" +msgid "" +"Template structure validation found the following issues, please fix and " +"retry:" msgstr "模板结构校验发现以下问题,请修复后重试:" #: src/iac_code/ui/banner.py:42 src/iac_code/ui/banner.py:54 @@ -1975,28 +2134,30 @@ msgstr "发行说明" msgid "Run {} to update." msgstr "运行 {} 进行更新。" -#: src/iac_code/ui/banner.py:105 +#: src/iac_code/ui/banner.py:110 msgid "Your AI-powered Infrastructure as Code assistant" msgstr "您的 AI 驱动的基础设施即代码助手" -#: src/iac_code/ui/banner.py:131 +#: src/iac_code/ui/banner.py:144 msgid "Welcome back" msgstr "欢迎回来" -#: src/iac_code/ui/banner.py:148 +#: src/iac_code/ui/banner.py:161 msgid "Debug mode" msgstr "调试模式" -#: src/iac_code/ui/banner.py:149 +#: src/iac_code/ui/banner.py:162 msgid "Log file" msgstr "日志文件" -#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 src/iac_code/ui/renderer.py:1455 +#: src/iac_code/ui/renderer.py:388 src/iac_code/ui/renderer.py:668 +#: src/iac_code/ui/renderer.py:1455 #, python-brace-format msgid "Thought for {seconds:.1f}s" msgstr "思考完成(耗时 {seconds:.1f}s)" -#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 src/iac_code/ui/renderer.py:1476 +#: src/iac_code/ui/renderer.py:404 src/iac_code/ui/renderer.py:700 +#: src/iac_code/ui/renderer.py:1476 msgid "(ctrl+o to expand)" msgstr "(ctrl+o 展开)" @@ -2082,97 +2243,109 @@ msgstr "否,始终拒绝 \"{rule}\"(本次会话)" msgid "No, always reject this tool" msgstr "否,始终拒绝此工具" -#: src/iac_code/ui/repl.py:406 +#: src/iac_code/ui/repl.py:412 msgid "Press Ctrl+C again to exit." msgstr "再次按 Ctrl+C 退出。" -#: src/iac_code/ui/repl.py:431 +#: src/iac_code/ui/repl.py:437 msgid "Interrupted." msgstr "已中断。" -#: src/iac_code/ui/repl.py:468 -msgid "Goodbye!" -msgstr "再见!" - -#: src/iac_code/ui/repl.py:469 -msgid "Resume this session with:" -msgstr "恢复此会话请运行:" - -#: src/iac_code/ui/repl.py:494 +#: src/iac_code/ui/repl.py:496 msgid "Update now" msgstr "立即更新" -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:498 msgid "Run the shown update command and exit when it succeeds." msgstr "运行显示的更新命令,成功后退出。" -#: src/iac_code/ui/repl.py:499 +#: src/iac_code/ui/repl.py:501 msgid "Skip" msgstr "跳过" -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:503 msgid "Continue with the current version for this session." msgstr "本次会话继续使用当前版本。" -#: src/iac_code/ui/repl.py:504 +#: src/iac_code/ui/repl.py:506 msgid "Skip until next version" msgstr "跳过直到下一个版本" -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:508 msgid "Hide this update until a newer version is available." msgstr "隐藏此更新,直到有更新的版本可用。" -#: src/iac_code/ui/repl.py:525 src/iac_code/ui/repl.py:537 +#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 msgid "Update command failed. Continuing with the current version." msgstr "更新命令失败。将继续使用当前版本。" -#: src/iac_code/ui/repl.py:530 +#: src/iac_code/ui/repl.py:532 msgid "Update completed. Restart iac-code to continue." msgstr "更新已完成。请重启 iac-code 以继续。" -#: src/iac_code/ui/repl.py:568 +#: src/iac_code/ui/repl.py:570 msgid "No image in clipboard." msgstr "剪贴板中没有图像。" -#: src/iac_code/ui/repl.py:754 +#: src/iac_code/ui/repl.py:756 msgid "Usage: !" msgstr "用法:!" -#: src/iac_code/ui/repl.py:759 +#: src/iac_code/ui/repl.py:761 msgid "Shell command support is unavailable." msgstr "Shell 命令支持不可用。" -#: src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:828 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "未知技能:${name}。输入 / 可列出命令和技能。" -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:830 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "未知命令:/{name}。输入 /help 查看可用命令。" -#: src/iac_code/ui/repl.py:833 +#: src/iac_code/ui/repl.py:835 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ 只能调用技能。请改用 /{name}。" -#: src/iac_code/ui/repl.py:855 src/iac_code/ui/repl.py:900 +#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 #, python-brace-format msgid "Command error: {error}" msgstr "命令错误:{error}" -#: src/iac_code/ui/repl.py:862 +#: src/iac_code/ui/repl.py:864 #, python-brace-format msgid "Command has no handler: {name}" msgstr "命令没有处理器:{name}" -#: src/iac_code/ui/repl.py:1167 +#: src/iac_code/ui/repl.py:1126 +msgid "Goodbye!" +msgstr "再见!" + +#: src/iac_code/ui/repl.py:1127 +msgid "Resume this session with:" +msgstr "恢复此会话请运行:" + +#: src/iac_code/ui/repl.py:1130 +msgid "Session ID" +msgstr "会话 ID" + +#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 #, python-brace-format msgid "Session not found: {session_id}" msgstr "会话不存在:{session_id}" -#: src/iac_code/ui/repl.py:1186 +#: src/iac_code/ui/repl.py:1233 +msgid "Session name: " +msgstr "会话名称:" + +#: src/iac_code/ui/repl.py:1239 +msgid "Session name cannot be empty." +msgstr "会话名称不能为空。" + +#: src/iac_code/ui/repl.py:1251 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2183,30 +2356,38 @@ msgstr "" "请运行以下命令恢复:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1287 +#: src/iac_code/ui/repl.py:1257 +msgid "Multiple sessions match. Resume one by ID:" +msgstr "匹配到多个会话。请通过 ID 恢复其中一个:" + +#: src/iac_code/ui/repl.py:1370 msgid "This conversation is from a different directory." msgstr "该会话来自另一个目录。" -#: src/iac_code/ui/repl.py:1289 +#: src/iac_code/ui/repl.py:1372 msgid "To resume, run:" msgstr "请运行以下命令恢复:" -#: src/iac_code/ui/repl.py:1294 +#: src/iac_code/ui/repl.py:1377 msgid "(Command copied to clipboard)" msgstr "(命令已复制到剪贴板)" -#: src/iac_code/ui/repl.py:1451 +#: src/iac_code/ui/repl.py:1534 #, python-brace-format -msgid "Current model {model} does not support image input. Use /model to switch to a vision-capable model." +msgid "" +"Current model {model} does not support image input. Use /model to switch " +"to a vision-capable model." msgstr "当前模型 {model} 不支持图像输入。请使用 /model 切换到支持视觉的模型。" -#: src/iac_code/ui/repl.py:1460 +#: src/iac_code/ui/repl.py:1543 #, python-brace-format msgid "Image error: {err}" msgstr "图像错误:{err}" -#: src/iac_code/ui/repl.py:1477 -msgid "Failed to persist image to cache; it will only exist in memory for this turn." +#: src/iac_code/ui/repl.py:1560 +msgid "" +"Failed to persist image to cache; it will only exist in memory for this " +"turn." msgstr "无法将图像持久化到缓存;本轮对话期间它仅存在于内存中。" #: src/iac_code/ui/spinner.py:52 @@ -2297,87 +2478,87 @@ msgstr "输入以搜索文件..." msgid "No matching files" msgstr "没有匹配的文件" -#: src/iac_code/ui/dialogs/resume_picker.py:114 +#: src/iac_code/ui/dialogs/resume_picker.py:116 msgid "Search..." msgstr "搜索…" -#: src/iac_code/ui/dialogs/resume_picker.py:359 +#: src/iac_code/ui/dialogs/resume_picker.py:374 msgid "Resume Session" msgstr "恢复会话" -#: src/iac_code/ui/dialogs/resume_picker.py:369 +#: src/iac_code/ui/dialogs/resume_picker.py:384 msgid "No sessions found" msgstr "未找到会话" -#: src/iac_code/ui/dialogs/resume_picker.py:427 +#: src/iac_code/ui/dialogs/resume_picker.py:444 msgid "show current dir" msgstr "显示当前目录" -#: src/iac_code/ui/dialogs/resume_picker.py:429 +#: src/iac_code/ui/dialogs/resume_picker.py:446 msgid "show all projects" msgstr "显示所有项目" -#: src/iac_code/ui/dialogs/resume_picker.py:432 +#: src/iac_code/ui/dialogs/resume_picker.py:449 msgid "show all branches" msgstr "显示所有分支" -#: src/iac_code/ui/dialogs/resume_picker.py:434 +#: src/iac_code/ui/dialogs/resume_picker.py:451 msgid "only show current branch" msgstr "仅显示当前分支" -#: src/iac_code/ui/dialogs/resume_picker.py:435 +#: src/iac_code/ui/dialogs/resume_picker.py:452 msgid "preview" msgstr "预览" -#: src/iac_code/ui/dialogs/resume_picker.py:436 +#: src/iac_code/ui/dialogs/resume_picker.py:453 msgid "Type to search" msgstr "输入以搜索" -#: src/iac_code/ui/dialogs/resume_picker.py:437 +#: src/iac_code/ui/dialogs/resume_picker.py:454 msgid "cancel" msgstr "取消" -#: src/iac_code/ui/dialogs/resume_picker.py:552 +#: src/iac_code/ui/dialogs/resume_picker.py:569 #, python-brace-format msgid "{n} more line{s}" msgstr "还有 {n} 行" -#: src/iac_code/ui/dialogs/resume_picker.py:566 +#: src/iac_code/ui/dialogs/resume_picker.py:583 #, python-brace-format msgid "{n} message{s}" msgstr "{n} 条消息" -#: src/iac_code/ui/dialogs/resume_picker.py:580 +#: src/iac_code/ui/dialogs/resume_picker.py:597 msgid "resume" msgstr "恢复" -#: src/iac_code/ui/dialogs/resume_picker.py:584 +#: src/iac_code/ui/dialogs/resume_picker.py:601 msgid "back" msgstr "返回" -#: src/iac_code/ui/dialogs/resume_picker.py:589 +#: src/iac_code/ui/dialogs/resume_picker.py:606 msgid "scroll" msgstr "滚动" -#: src/iac_code/ui/dialogs/resume_picker.py:608 +#: src/iac_code/ui/dialogs/resume_picker.py:625 msgid "(empty session)" msgstr "(空会话)" -#: src/iac_code/ui/dialogs/resume_picker.py:728 +#: src/iac_code/ui/dialogs/resume_picker.py:745 msgid "just now" msgstr "刚刚" -#: src/iac_code/ui/dialogs/resume_picker.py:731 +#: src/iac_code/ui/dialogs/resume_picker.py:748 #, python-brace-format msgid "{n} minute{s} ago" msgstr "{n} 分钟前" -#: src/iac_code/ui/dialogs/resume_picker.py:734 +#: src/iac_code/ui/dialogs/resume_picker.py:751 #, python-brace-format msgid "{n} hour{s} ago" msgstr "{n} 小时前" -#: src/iac_code/ui/dialogs/resume_picker.py:736 +#: src/iac_code/ui/dialogs/resume_picker.py:753 #, python-brace-format msgid "{n} day{s} ago" msgstr "{n} 天前" @@ -2397,7 +2578,9 @@ msgstr "{current} / {total}" #: src/iac_code/ui/dialogs/skills_picker.py:165 #, python-brace-format -msgid "{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to cancel" +msgid "" +"{count} skills - Space to toggle, Enter to save, Tab to sort, Esc to " +"cancel" msgstr "{count} 个技能 - 空格切换,Enter 保存,Tab 排序,Esc 取消" #: src/iac_code/ui/dialogs/skills_picker.py:171 @@ -2484,7 +2667,9 @@ msgid "iac-code on Windows requires Git for Windows." msgstr "iac-code 在 Windows 上需要安装 Git for Windows。" #: src/iac_code/utils/platform.py:41 -msgid "If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment variable." +msgid "" +"If installed but not on PATH, set IAC_CODE_GIT_BASH_PATH environment " +"variable." msgstr "如果已安装但不在 PATH 中,请设置 IAC_CODE_GIT_BASH_PATH 环境变量。" #: src/iac_code/utils/platform.py:44 @@ -2496,7 +2681,9 @@ msgid " Option 1 - winget (requires access to github.com):" msgstr " 方式 1 - winget(需要能访问 github.com):" #: src/iac_code/utils/platform.py:52 -msgid " Option 2 - if you cannot reach github.com, run this to install via npmmirror:" +msgid "" +" Option 2 - if you cannot reach github.com, run this to install via " +"npmmirror:" msgstr " 方式 2 - 如果无法访问 github.com,运行以下命令通过 npmmirror 安装:" #~ msgid "toggle preview" @@ -2520,7 +2707,10 @@ msgstr " 方式 2 - 如果无法访问 github.com,运行以下命令通过 np #~ msgid "tree-sitter not available" #~ msgstr "tree-sitter 不可用" -#~ msgid "LLM provider is locked by '{source}'. To change, modify llm_source in settings.yml." +#~ msgid "" +#~ "LLM provider is locked by '{source}'." +#~ " To change, modify llm_source in " +#~ "settings.yml." #~ msgstr "LLM 提供商已被 '{source}' 锁定。如需修改,请调整 settings.yml 中的 llm_source 配置。" #~ msgid "Provider switched: {status}" @@ -2538,6 +2728,12 @@ msgstr " 方式 2 - 如果无法访问 github.com,运行以下命令通过 np #~ msgid "Cache create" #~ msgstr "缓存创建" -#~ msgid "{count} skills - Space to toggle, Enter to save, / to search, t to sort, Esc to cancel" +#~ msgid "" +#~ "{count} skills - Space to toggle, " +#~ "Enter to save, / to search, t " +#~ "to sort, Esc to cancel" #~ msgstr "{count} 个技能 - 空格切换,Enter 保存,/ 搜索,t 排序,Esc 取消" +#~ msgid "Resume a session by ID" +#~ msgstr "通过 ID 恢复会话" + diff --git a/src/iac_code/services/session_index.py b/src/iac_code/services/session_index.py index d7a3e41..dcfc128 100644 --- a/src/iac_code/services/session_index.py +++ b/src/iac_code/services/session_index.py @@ -14,7 +14,13 @@ from dataclasses import dataclass from pathlib import Path -from iac_code.utils.project_paths import get_project_dir, get_projects_dir, is_conversation_session_file, sanitize_path +from iac_code.services.session_metadata import SESSION_JSONL_FILENAME, read_session_metadata +from iac_code.utils.project_paths import ( + get_project_dir, + get_projects_dir, + is_conversation_session_file, + sanitize_path, +) LITE_READ_BUF_SIZE = 64 * 1024 @@ -36,6 +42,9 @@ class SessionEntry: title: str mtime: float size_bytes: int + name: str | None = None + auto_title: str | None = None + is_legacy: bool = True # --------------------------------------------------------------------------- @@ -199,23 +208,45 @@ def _trim_title(text: str, max_len: int = 200) -> str: return flat[:max_len].rstrip() + "…" -def _build_entry(path: Path, fallback_cwd: str) -> SessionEntry | None: +def _iter_session_files(project_dir: Path) -> list[tuple[Path, str]]: + files_by_session_id = { + jsonl.stem: jsonl for jsonl in project_dir.glob("*.jsonl") if is_conversation_session_file(jsonl) + } + for session_dir in project_dir.iterdir(): + if not session_dir.is_dir(): + continue + jsonl = session_dir / SESSION_JSONL_FILENAME + if jsonl.exists(): + files_by_session_id[session_dir.name] = jsonl + return [(jsonl, session_id) for session_id, jsonl in files_by_session_id.items()] + + +def _build_entry(path: Path, fallback_cwd: str, session_id: str | None = None) -> SessionEntry | None: try: stat = path.stat() except OSError: return None - meta = read_lite_metadata(path) - cwd = meta.cwd or fallback_cwd - title_raw = meta.last_prompt or meta.first_prompt or "" - title = _trim_title(title_raw) if title_raw else "(empty)" + lite_meta = read_lite_metadata(path) + path_session_id = session_id or path.stem + directory_metadata = read_session_metadata(path.parent) if path.name == SESSION_JSONL_FILENAME else None + if directory_metadata and directory_metadata.session_id != path_session_id: + directory_metadata = None + name = directory_metadata.name if directory_metadata else None + auto_title_raw = lite_meta.last_prompt or lite_meta.first_prompt + auto_title = _trim_title(auto_title_raw) if auto_title_raw else None + cwd = (directory_metadata.cwd if directory_metadata else None) or lite_meta.cwd or fallback_cwd + title = name or auto_title or "(empty)" return SessionEntry( - session_id=path.stem, + session_id=path_session_id, cwd=cwd, project_name=os.path.basename(cwd) if cwd else "?", - git_branch=meta.git_branch, + git_branch=(directory_metadata.git_branch if directory_metadata else None) or lite_meta.git_branch, title=title, mtime=stat.st_mtime, size_bytes=stat.st_size, + name=name, + auto_title=auto_title, + is_legacy=path.name != SESSION_JSONL_FILENAME, ) @@ -238,10 +269,8 @@ def list_for_cwd(self, cwd: str) -> list[SessionEntry]: if not project_dir.exists(): return [] entries: list[SessionEntry] = [] - for jsonl in project_dir.glob("*.jsonl"): - if not is_conversation_session_file(jsonl): - continue - entry = _build_entry(jsonl, fallback_cwd=cwd) + for jsonl, session_id in _iter_session_files(project_dir): + entry = _build_entry(jsonl, fallback_cwd=cwd, session_id=session_id) if entry is not None: entries.append(entry) entries.sort(key=lambda e: e.mtime, reverse=True) @@ -255,10 +284,8 @@ def list_all_projects(self) -> list[SessionEntry]: for proj_dir in self._projects_dir.iterdir(): if not proj_dir.is_dir(): continue - for jsonl in proj_dir.glob("*.jsonl"): - if not is_conversation_session_file(jsonl): - continue - entry = _build_entry(jsonl, fallback_cwd="") + for jsonl, session_id in _iter_session_files(proj_dir): + entry = _build_entry(jsonl, fallback_cwd="", session_id=session_id) if entry is not None: entries.append(entry) entries.sort(key=lambda e: e.mtime, reverse=True) @@ -268,20 +295,11 @@ def find_by_id_or_prefix(self, arg: str) -> SessionEntry | None: """Locate a single entry by exact session id or unique id prefix.""" if not self._projects_dir.exists() or not arg: return None - matches: list[SessionEntry] = [] - for proj_dir in self._projects_dir.iterdir(): - if not proj_dir.is_dir(): - continue - for jsonl in proj_dir.glob("*.jsonl"): - if not is_conversation_session_file(jsonl): - continue - sid = jsonl.stem - if sid == arg: - return _build_entry(jsonl, fallback_cwd="") - if sid.startswith(arg): - entry = _build_entry(jsonl, fallback_cwd="") - if entry is not None: - matches.append(entry) + entries = self.list_all_projects() + for entry in entries: + if entry.session_id == arg: + return entry + matches = [entry for entry in entries if entry.session_id.startswith(arg)] if len(matches) == 1: return matches[0] return None diff --git a/src/iac_code/services/session_metadata.py b/src/iac_code/services/session_metadata.py new file mode 100644 index 0000000..fb034f0 --- /dev/null +++ b/src/iac_code/services/session_metadata.py @@ -0,0 +1,78 @@ +"""Session metadata primitives.""" + +from __future__ import annotations + +import json +import re +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any + +from iac_code.i18n import _ +from iac_code.utils.file_security import ensure_private_dir, ensure_private_file + +SESSION_JSONL_FILENAME = "session.jsonl" +SESSION_METADATA_FILENAME = "metadata.json" +SESSION_NAME_PATTERN_TEXT = r"^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$" +SESSION_NAME_PATTERN = re.compile(SESSION_NAME_PATTERN_TEXT) +SESSION_METADATA_SCHEMA_VERSION = 1 + + +@dataclass(frozen=True) +class SessionMetadata: + session_id: str + name: str | None = None + cwd: str | None = None + git_branch: str | None = None + created_at: str | None = None + updated_at: str | None = None + schema_version: int = SESSION_METADATA_SCHEMA_VERSION + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> SessionMetadata | None: + session_id = data.get("session_id") + if not isinstance(session_id, str) or not session_id: + return None + + name = data.get("name") + schema_version = data.get("schema_version") + return cls( + session_id=session_id, + name=name if isinstance(name, str) and name else None, + cwd=_string_or_none(data.get("cwd")), + git_branch=_string_or_none(data.get("git_branch")), + created_at=_string_or_none(data.get("created_at")), + updated_at=_string_or_none(data.get("updated_at")), + schema_version=schema_version if type(schema_version) is int else SESSION_METADATA_SCHEMA_VERSION, + ) + + +def validate_session_name(name: str) -> str: + if not SESSION_NAME_PATTERN.fullmatch(name): + raise ValueError(_("Session name must match {pattern}").format(pattern=SESSION_NAME_PATTERN_TEXT)) + return name + + +def normalize_session_name(name: str) -> str: + return validate_session_name(name.strip()) + + +def read_session_metadata(session_dir: Path) -> SessionMetadata | None: + try: + data = json.loads((session_dir / SESSION_METADATA_FILENAME).read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return None + if not isinstance(data, dict): + return None + return SessionMetadata.from_dict(data) + + +def write_session_metadata(session_dir: Path, metadata: SessionMetadata) -> None: + ensure_private_dir(session_dir) + path = session_dir / SESSION_METADATA_FILENAME + path.write_text(json.dumps(asdict(metadata), ensure_ascii=False) + "\n", encoding="utf-8") + ensure_private_file(path) + + +def _string_or_none(value: object) -> str | None: + return value if isinstance(value, str) else None diff --git a/src/iac_code/services/session_resolver.py b/src/iac_code/services/session_resolver.py new file mode 100644 index 0000000..ab20871 --- /dev/null +++ b/src/iac_code/services/session_resolver.py @@ -0,0 +1,73 @@ +"""Shared session argument resolver.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum + +from iac_code.services.session_index import SessionEntry, SessionIndex + + +class ResolutionStatus(str, Enum): + FOUND = "found" + NOT_FOUND = "not_found" + AMBIGUOUS_NAME = "ambiguous_name" + + +@dataclass(frozen=True) +class SessionResolution: + status: ResolutionStatus + entry: SessionEntry | None = None + candidates: list[SessionEntry] = field(default_factory=list) + + +def resolve_session_argument(index: SessionIndex, current_cwd: str, arg: str) -> SessionResolution: + needle = arg.strip() + if not needle: + return SessionResolution(status=ResolutionStatus.NOT_FOUND) + + current_entries = index.list_for_cwd(current_cwd) + entry = _exact_id(current_entries, needle) + if entry is not None: + return SessionResolution(status=ResolutionStatus.FOUND, entry=entry) + + current_prefix_matches = _id_prefix_matches(current_entries, needle) + if len(current_prefix_matches) == 1: + return SessionResolution(status=ResolutionStatus.FOUND, entry=current_prefix_matches[0]) + if len(current_prefix_matches) > 1: + return SessionResolution(status=ResolutionStatus.NOT_FOUND) + + entry = _exact_name(current_entries, needle) + if entry is not None: + return SessionResolution(status=ResolutionStatus.FOUND, entry=entry) + + all_entries = index.list_all_projects() + entry = _exact_id(all_entries, needle) + if entry is not None: + return SessionResolution(status=ResolutionStatus.FOUND, entry=entry) + + global_prefix_matches = _id_prefix_matches(all_entries, needle) + if len(global_prefix_matches) == 1: + return SessionResolution(status=ResolutionStatus.FOUND, entry=global_prefix_matches[0]) + if len(global_prefix_matches) > 1: + return SessionResolution(status=ResolutionStatus.NOT_FOUND) + + name_matches = [entry for entry in all_entries if entry.name == needle] + if len(name_matches) == 1: + return SessionResolution(status=ResolutionStatus.FOUND, entry=name_matches[0]) + if len(name_matches) > 1: + return SessionResolution(status=ResolutionStatus.AMBIGUOUS_NAME, candidates=name_matches) + + return SessionResolution(status=ResolutionStatus.NOT_FOUND) + + +def _exact_id(entries: list[SessionEntry], arg: str) -> SessionEntry | None: + return next((entry for entry in entries if entry.session_id == arg), None) + + +def _id_prefix_matches(entries: list[SessionEntry], arg: str) -> list[SessionEntry]: + return [entry for entry in entries if entry.session_id.startswith(arg)] + + +def _exact_name(entries: list[SessionEntry], arg: str) -> SessionEntry | None: + return next((entry for entry in entries if entry.name == arg), None) diff --git a/src/iac_code/services/session_storage.py b/src/iac_code/services/session_storage.py index a652267..1dc1662 100644 --- a/src/iac_code/services/session_storage.py +++ b/src/iac_code/services/session_storage.py @@ -2,9 +2,13 @@ Layout:: - ~/.iac-code/projects//.jsonl + ~/.iac-code/projects///session.jsonl + ~/.iac-code/projects///metadata.json -Each session file is a stream of two kinds of JSONL lines: +Legacy sessions at ``.jsonl`` remain readable and are +migrated to the directory format when renamed. + +Each ``session.jsonl`` file is a stream of two kinds of JSONL lines: * **Message rows** — one per :class:`Message`, with extra stamp fields (``session_id``, ``cwd``, ``git_branch``, ``version``) appended at write @@ -19,11 +23,21 @@ from __future__ import annotations import json +from datetime import datetime, timezone from pathlib import Path +from shutil import move from typing import Any from iac_code import __version__ from iac_code.agent.message import ContentBlock, Message, ToolResultBlock +from iac_code.i18n import _ +from iac_code.services.session_metadata import ( + SESSION_JSONL_FILENAME, + SessionMetadata, + normalize_session_name, + read_session_metadata, + write_session_metadata, +) from iac_code.utils.file_security import ensure_private_dir, ensure_private_file from iac_code.utils.project_paths import ( get_project_dir, @@ -33,6 +47,10 @@ ) +def _utc_now() -> str: + return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + + class SessionStorage: """Persist conversation sessions partitioned by working directory.""" @@ -43,7 +61,7 @@ def __init__(self, projects_dir: Path | str | None = None) -> None: # Internal path helpers # ------------------------------------------------------------------ - def _session_path(self, cwd: str, session_id: str) -> Path: + def _legacy_session_path(self, cwd: str, session_id: str) -> Path: if self._projects_dir == get_projects_dir(): return get_session_path(cwd, session_id) from iac_code.utils.project_paths import sanitize_path @@ -57,10 +75,34 @@ def _project_dir_for(self, cwd: str) -> Path: return self._projects_dir / sanitize_path(cwd) + def _session_dir(self, cwd: str, session_id: str) -> Path: + return self._project_dir_for(cwd) / session_id + + def _directory_session_path(self, cwd: str, session_id: str) -> Path: + return self._session_dir(cwd, session_id) / SESSION_JSONL_FILENAME + + def _session_path(self, cwd: str, session_id: str) -> Path: + directory_path = self._directory_session_path(cwd, session_id) + legacy_path = self._legacy_session_path(cwd, session_id) + if directory_path.exists(): + return directory_path + if legacy_path.exists(): + return legacy_path + return directory_path + def session_path(self, cwd: str, session_id: str) -> Path: """Public accessor for the on-disk JSONL path of a session.""" return self._session_path(cwd, session_id) + def legacy_session_path(self, cwd: str, session_id: str) -> Path: + return self._legacy_session_path(cwd, session_id) + + def session_dir(self, cwd: str, session_id: str) -> Path: + return self._session_dir(cwd, session_id) + + def read_metadata(self, cwd: str, session_id: str) -> SessionMetadata | None: + return read_session_metadata(self._session_dir(cwd, session_id)) + # ------------------------------------------------------------------ # Stamp helpers # ------------------------------------------------------------------ @@ -156,6 +198,60 @@ def load(self, cwd: str, session_id: str) -> list[Message]: def exists(self, cwd: str, session_id: str) -> bool: return self._session_path(cwd, session_id).exists() + # ------------------------------------------------------------------ + # Rename / migration + # ------------------------------------------------------------------ + + def _iter_project_session_dirs(self, cwd: str) -> list[Path]: + project_dir = self._project_dir_for(cwd) + if not project_dir.exists(): + return [] + return [p for p in project_dir.iterdir() if p.is_dir() and (p / SESSION_JSONL_FILENAME).exists()] + + def _name_owner_in_project(self, cwd: str, name: str) -> str | None: + for session_dir in self._iter_project_session_dirs(cwd): + metadata = read_session_metadata(session_dir) + if metadata and metadata.name == name: + return metadata.session_id + return None + + def _ensure_directory_format(self, cwd: str, session_id: str) -> Path: + session_dir = self._session_dir(cwd, session_id) + directory_path = session_dir / SESSION_JSONL_FILENAME + if directory_path.exists(): + return session_dir + legacy_path = self._legacy_session_path(cwd, session_id) + if not legacy_path.exists(): + ensure_private_dir(session_dir) + directory_path.touch() + ensure_private_file(directory_path) + return session_dir + ensure_private_dir(session_dir) + move(str(legacy_path), str(directory_path)) + ensure_private_file(directory_path) + return session_dir + + def rename_session(self, cwd: str, session_id: str, name: str, *, git_branch: str | None = None) -> str: + normalized = normalize_session_name(name) + current = self.read_metadata(cwd, session_id) + if current and current.name == normalized: + return "unchanged" + owner = self._name_owner_in_project(cwd, normalized) + if owner is not None and owner != session_id: + raise ValueError(_("Session name already exists in this project: {name}").format(name=normalized)) + session_dir = self._ensure_directory_format(cwd, session_id) + now = _utc_now() + metadata = SessionMetadata( + session_id=session_id, + name=normalized, + cwd=cwd, + git_branch=git_branch, + created_at=current.created_at if current else now, + updated_at=now, + ) + write_session_metadata(session_dir, metadata) + return "renamed" + # ------------------------------------------------------------------ # Cross-project lookups (used by CLI --resume / --continue) # ------------------------------------------------------------------ @@ -172,6 +268,10 @@ def find_session_anywhere(self, session_id: str) -> tuple[str, Path] | None: for proj_dir in self._projects_dir.iterdir(): if not proj_dir.is_dir(): continue + candidate = proj_dir / session_id / SESSION_JSONL_FILENAME + if candidate.exists(): + cwd = self._read_cwd_from_file(candidate) or "" + return cwd, candidate candidate = proj_dir / f"{session_id}.jsonl" if candidate.exists() and is_conversation_session_file(candidate): cwd = self._read_cwd_from_file(candidate) or "" @@ -186,6 +286,14 @@ def get_latest_session_anywhere(self) -> tuple[str, str] | None: for proj_dir in self._projects_dir.iterdir(): if not proj_dir.is_dir(): continue + for session_dir in proj_dir.iterdir(): + if not session_dir.is_dir(): + continue + jsonl = session_dir / SESSION_JSONL_FILENAME + if jsonl.exists(): + mtime = jsonl.stat().st_mtime + if latest is None or mtime > latest[0]: + latest = (mtime, jsonl) for jsonl in proj_dir.glob("*.jsonl"): if not is_conversation_session_file(jsonl): continue @@ -196,7 +304,7 @@ def get_latest_session_anywhere(self) -> tuple[str, str] | None: return None path = latest[1] cwd = self._read_cwd_from_file(path) or "" - session_id = path.stem + session_id = path.parent.name if path.name == SESSION_JSONL_FILENAME else path.stem return cwd, session_id @staticmethod diff --git a/src/iac_code/ui/banner.py b/src/iac_code/ui/banner.py index 91c83f4..31b745b 100644 --- a/src/iac_code/ui/banner.py +++ b/src/iac_code/ui/banner.py @@ -85,7 +85,12 @@ def _get_provider_display() -> str: return "" -def render_welcome_banner(model: str, cwd: str, session_id: str | None = None) -> Panel: +def render_welcome_banner( + model: str, + cwd: str, + session_id: str | None = None, + session_name: str | None = None, +) -> Panel: """Produce a Rich Panel for the welcome banner.""" # Username try: @@ -126,6 +131,14 @@ def render_welcome_banner(model: str, cwd: str, session_id: str | None = None) - from iac_code import __version__ + session_display: Text + if session_name and session_id: + session_display = Text(" {}: {} ({})".format(_("Session"), session_name, session_id), style="dim") + elif session_id: + session_display = Text(" {}: {}".format(_("Session"), session_id), style="dim") + else: + session_display = Text() + items = [ Text(), Text(" {} {}!".format(_("Welcome back"), username), style="bold"), @@ -135,7 +148,7 @@ def render_welcome_banner(model: str, cwd: str, session_id: str | None = None) - Text(f" iac-code v{__version__}", style="dim"), Text(f" {model_display}", style="dim") if model_display else Text(), Text(f" {cwd_display}", style="dim"), - Text(" {}: {}".format(_("Session"), session_id), style="dim") if session_id else Text(), + session_display, ] from iac_code.utils.log import is_debug_enabled diff --git a/src/iac_code/ui/dialogs/resume_picker.py b/src/iac_code/ui/dialogs/resume_picker.py index 19b0216..9f7dc62 100644 --- a/src/iac_code/ui/dialogs/resume_picker.py +++ b/src/iac_code/ui/dialogs/resume_picker.py @@ -64,11 +64,13 @@ def __init__( current_session_id: str | None, keybinding_manager: object | None = None, renderer: "Renderer | None" = None, + entries: list[SessionEntry] | None = None, ) -> None: self._index = index self._current_cwd = current_cwd self._current_session_id = current_session_id self._km = keybinding_manager + self._entries_override = entries # Live REPL renderer — reused inside the preview so the dump # uses the same tool-name translation, argument formatting, and # result-summary helpers as the live UI. @@ -285,7 +287,9 @@ def _toggle_only_current_branch(self) -> None: self._apply_filter() def _reload_entries(self) -> None: - if self._show_all_projects: + if self._entries_override is not None: + entries = list(self._entries_override) + elif self._show_all_projects: entries = self._index.list_all_projects() else: entries = self._index.list_for_cwd(self._current_cwd) @@ -304,7 +308,18 @@ def _apply_filter(self) -> None: else: scored: list[tuple[float, SessionEntry]] = [] for entry in candidates: - haystack = " ".join(part for part in (entry.title, entry.project_name, entry.git_branch or "") if part) + haystack = " ".join( + part + for part in ( + entry.name, + entry.session_id, + entry.title, + entry.auto_title, + entry.project_name, + entry.git_branch or "", + ) + if part + ) if entry.session_id.startswith(query): scored.append((1_000_000.0, entry)) continue @@ -415,6 +430,8 @@ def _render_title_line(entry: SessionEntry, is_focused: bool) -> Text: def _render_subtitle_line(entry: SessionEntry) -> Text: text = Text(" ", style="dim") parts = [_format_relative_time(entry.mtime)] + if entry.name: + parts.append(_short_session_id(entry.session_id)) if entry.git_branch: parts.append(entry.git_branch) parts.append(_format_size(entry.size_bytes)) @@ -747,3 +764,9 @@ def _format_size(size_bytes: int) -> str: return f"{mb:.1f}MB" gb = mb / 1024 return f"{gb:.1f}GB" + + +def _short_session_id(session_id: str) -> str: + if len(session_id) <= 8: + return session_id + return session_id[:8] diff --git a/src/iac_code/ui/repl.py b/src/iac_code/ui/repl.py index 22c54ea..6800040 100644 --- a/src/iac_code/ui/repl.py +++ b/src/iac_code/ui/repl.py @@ -16,6 +16,7 @@ import os import re import signal +import subprocess import sys import time from dataclasses import dataclass @@ -28,7 +29,7 @@ from iac_code.agent.agent_loop import AgentLoop from iac_code.agent.system_prompt import build_system_prompt from iac_code.commands import create_default_registry -from iac_code.commands.registry import LocalCommand, PromptCommand +from iac_code.commands.registry import CommandResult, LocalCommand, PromptCommand from iac_code.config import get_active_provider_key, get_config_dir, get_history_path, load_credentials from iac_code.i18n import _ from iac_code.memory.memory_manager import MemoryManager @@ -36,6 +37,8 @@ from iac_code.providers.registry import PROVIDER_REGISTRY from iac_code.services.cloud_credentials import CloudCredentials from iac_code.services.session_index import SessionIndex +from iac_code.services.session_metadata import normalize_session_name +from iac_code.services.session_resolver import ResolutionStatus, resolve_session_argument from iac_code.services.session_storage import SessionStorage from iac_code.services.update_checker import ( PendingUpdate, @@ -88,6 +91,14 @@ class CommandContext: repl: "InlineREPL" +def _normalize_command_result(result: object) -> tuple[str, bool, bool]: + if result is None: + return "", False, False + if isinstance(result, CommandResult): + return result.message, result.is_error, result.refresh_banner + return str(result), False, False + + class InlineREPL: """Inline terminal REPL integrating all subsystems.""" @@ -137,6 +148,7 @@ def __init__( self._image_store = ImageStore(session_id=self._session_id) self._resume_messages = self._load_resume_messages(resume_session_id) + self._session_name = self._load_current_session_name() self._task_manager = TaskManager() self._notification_queue = NotificationQueue() self._command_log: list[tuple[str, str, int, bool]] = [] @@ -325,7 +337,9 @@ async def run(self, initial_prompt: str | None = None) -> None: state = self.store.get_state() if startup_update is not None: self.console.print(render_update_notice(startup_update)) - self.console.print(render_welcome_banner(state.model, state.cwd, session_id=self._session_id)) + self.console.print( + render_welcome_banner(state.model, state.cwd, session_id=self._session_id, session_name=self._session_name) + ) if self._resume_messages: self.renderer.replay_history(self._resume_messages) self.console.print() # blank line before first new user turn @@ -463,11 +477,7 @@ def _on_sigint() -> None: except (termios.error, OSError, ValueError): pass - from rich.text import Text - - self.console.print("[dim]{}[/dim]".format(_("Goodbye!"))) - self.console.print(Text(_("Resume this session with:"), style="dim")) - self.console.print(Text(f"iac-code --resume {self._session_id}", style="dim")) + self._print_exit_text() async def run_once(self, prompt: str) -> None: """Process a single prompt and exit (non-interactive mode).""" @@ -751,12 +761,16 @@ async def _handle_shell_escape(self, user_input: str) -> None: """Execute a local shell command from a leading ! REPL input.""" command = user_input[1:].strip() if not command: - self.renderer.print_system_message(_("Usage: !"), style="yellow") + message = _("Usage: !") + self._record_command_log(user_input, message, is_error=True) + self.renderer.print_system_message(message, style="yellow") return tool = self.tool_registry.get("bash") if tool is None: - self.renderer.print_system_message(_("Shell command support is unavailable."), style="red") + message = _("Shell command support is unavailable.") + self._record_command_log(user_input, message, is_error=True) + self.renderer.print_system_message(message, style="red") return tool_input = {"command": command} @@ -776,6 +790,8 @@ async def _handle_shell_escape(self, user_input: str) -> None: output = result.content.rstrip() if output: self.renderer.print_system_message(output, style="red" if result.is_error else "white") + log_result = f"$ {command}" if not output else f"$ {command}\n{output}" + self._record_command_log(user_input, log_result, is_error=result.is_error) async def _request_shell_escape_permission(self, tool, tool_input: dict) -> bool: """Check permission for a display-only shell escape before execution.""" @@ -814,8 +830,7 @@ async def _handle_command(self, user_input: str) -> None: cmd = self.command_registry.get(name) def _emit_error(message: str) -> None: - msg_count = len(self._agent_loop.context_manager.get_messages()) - self._command_log.append((user_input, message, msg_count, True)) + self._record_command_log(user_input, message, is_error=True) self.renderer.print_system_message(message, style="red") if cmd is None: @@ -882,17 +897,20 @@ def _emit_error(message: str) -> None: self.store.set_state(is_busy=False) else: result = await handler_call - if result: - msg_count = len(self._agent_loop.context_manager.get_messages()) - self._command_log.append((user_input, result, msg_count, False)) + result_message, is_error, refresh_banner = _normalize_command_result(result) + if result_message: + self._record_command_log(user_input, result_message, is_error=is_error) # Re-render banner when model/provider actually switched new_state = self.store.get_state() new_provider_key = get_active_provider_key() - if new_state.model != prev_model or new_provider_key != prev_provider_key: + if refresh_banner or new_state.model != prev_model or new_provider_key != prev_provider_key: self._refresh_banner() else: - if result: - self.renderer.print_command_result(user_input, result) + if result_message: + if is_error: + self.renderer.print_system_message(result_message, style="red") + else: + self.renderer.print_command_result(user_input, result_message) except ExitREPLError: raise except Exception as exc: @@ -901,12 +919,24 @@ def _emit_error(message: str) -> None: style="red", ) + def _message_count(self) -> int: + try: + return len(self._agent_loop.context_manager.get_messages()) + except Exception: + return 0 + + def _record_command_log(self, user_input: str, result: str, *, is_error: bool) -> None: + if hasattr(self, "_command_log"): + self._command_log.append((user_input, result, self._message_count(), is_error)) + def _refresh_banner(self) -> None: """Clear screen and re-render the welcome banner, then replay history with commands.""" self.console.file.write("\033[H\033[2J\033[3J") self.console.file.flush() state = self.store.get_state() - self.console.print(render_welcome_banner(state.model, state.cwd, session_id=self._session_id)) + self.console.print( + render_welcome_banner(state.model, state.cwd, session_id=self._session_id, session_name=self._session_name) + ) messages = self._agent_loop.context_manager.get_messages() if not messages and not self._command_log and not self._streaming_error_log: return @@ -1114,6 +1144,17 @@ def _record_command_history(self, user_input: str) -> None: return self._history.append(user_input) + def _print_exit_text(self) -> None: + """Print the session resume hint shown when the REPL exits.""" + from rich.text import Text + + resume_arg = self._session_name or self._session_id + self.console.print("[dim]{}[/dim]".format(_("Goodbye!"))) + self.console.print(Text(_("Resume this session with:"), style="dim")) + self.console.print(Text("iac-code --resume {}".format(resume_arg), style="dim")) + if self._session_name: + self.console.print(Text("{}: {}".format(_("Session ID"), self._session_id), style="dim")) + def _apply_qwenpaw_config(self, model: str) -> None: """Apply QwenPaw config if active and env vars don't override.""" from iac_code.config import _get_env_overrides, get_llm_source @@ -1159,16 +1200,17 @@ def _resolve_session_id(self, resume: str | bool | None) -> str: if cwd and cwd != self._original_cwd: raise ValueError(self._cross_project_message(cwd, sid)) return sid - elif isinstance(resume, str) and resume: - if self._session_storage.exists(self._original_cwd, resume): - return resume - located = self._session_storage.find_session_anywhere(resume) - if located is None: + if isinstance(resume, str) and resume: + resolution = resolve_session_argument(self.session_index, self._original_cwd, resume) + if resolution.status == ResolutionStatus.NOT_FOUND: raise ValueError(_("Session not found: {session_id}").format(session_id=resume)) - cwd, _path = located - if cwd and cwd != self._original_cwd: - raise ValueError(self._cross_project_message(cwd, resume)) - return resume + if resolution.status == ResolutionStatus.AMBIGUOUS_NAME: + raise ValueError(self._ambiguous_resume_message(resolution.candidates)) + if resolution.entry is None: + raise ValueError(_("Session not found: {session_id}").format(session_id=resume)) + if resolution.entry.cwd and resolution.entry.cwd != self._original_cwd: + raise ValueError(self._cross_project_message(resolution.entry.cwd, resolution.entry.session_id)) + return resolution.entry.session_id return str(uuid.uuid4()) def _load_resume_messages(self, resume: str | bool | None) -> list: @@ -1178,6 +1220,55 @@ def _load_resume_messages(self, resume: str | bool | None) -> list: messages = self._session_storage.load(self._original_cwd, self._session_id) return self._session_storage.repair_interrupted(messages) + def _load_current_session_name(self) -> str | None: + """Read the persisted display name for the active session.""" + metadata = self._session_storage.read_metadata(self._original_cwd, self._session_id) + return metadata.name if metadata else None + + def current_git_branch(self) -> str | None: + """Return the current git branch for the REPL's original directory.""" + try: + result = subprocess.run( + ["git", "-C", self._original_cwd, "rev-parse", "--abbrev-ref", "HEAD"], + capture_output=True, + check=False, + text=True, + ) + except OSError: + return None + if result.returncode != 0: + return None + branch = result.stdout.strip() + return branch if branch and branch != "HEAD" else None + + def rename_current_session(self, name: str) -> str: + """Rename the active session and refresh cached session metadata.""" + result = self._session_storage.rename_session( + self._original_cwd, + self._session_id, + name, + git_branch=self.current_git_branch(), + ) + self._session_name = self._load_current_session_name() + return result + + async def prompt_for_session_name(self) -> str | None: + """Prompt until the user provides a valid session name or cancels.""" + while True: + try: + raw_name = await self._prompt_input.get_input(_("Session name: ")) + except (EOFError, KeyboardInterrupt): + return None + if raw_name is None: + return None + if not raw_name.strip(): + self.renderer.print_system_message(_("Session name cannot be empty."), style="red") + continue + try: + return normalize_session_name(raw_name) + except ValueError as exc: + self.renderer.print_system_message(str(exc), style="red") + @staticmethod def _cross_project_message(cwd: str, session_id: str) -> str: import shlex @@ -1185,6 +1276,16 @@ def _cross_project_message(cwd: str, session_id: str) -> str: cmd = f"cd {shlex.quote(cwd)} && iac-code --resume {session_id}" return _("This session belongs to a different directory.\nTo resume, run:\n {cmd}").format(cmd=cmd) + @staticmethod + def _ambiguous_resume_message(entries) -> str: + import shlex + + lines = [_("Multiple sessions match. Resume one by ID:"), ""] + for entry in entries: + cmd = f"cd {shlex.quote(entry.cwd)} && iac-code --resume {entry.session_id}" + lines.append(f" {cmd}") + return "\n".join(lines) + @property def session_id(self) -> str: return self._session_id @@ -1261,12 +1362,20 @@ def swap_session(self, new_session_id: str) -> None: self._agent_loop.replace_session(new_session_id, new_messages or None) self._session_id = new_session_id self._was_resumed = True + self._session_name = self._load_current_session_name() # Clear screen + scrollback, redraw banner, replay history. self.console.file.write("\033[H\033[2J\033[3J") self.console.file.flush() state = self.store.get_state() - self.console.print(render_welcome_banner(state.model, state.cwd, session_id=new_session_id)) + self.console.print( + render_welcome_banner( + state.model, + state.cwd, + session_id=new_session_id, + session_name=self._session_name, + ) + ) if new_messages: self.renderer.replay_history(new_messages) self.console.print() diff --git a/tests/acp/test_mcp.py b/tests/acp/test_mcp.py index 1f28d28..20dd256 100644 --- a/tests/acp/test_mcp.py +++ b/tests/acp/test_mcp.py @@ -10,6 +10,8 @@ from iac_code.acp.mcp import convert_mcp_configs from iac_code.acp.server import ACPServer +from iac_code.services.session_index import SessionEntry +from iac_code.services.session_resolver import ResolutionStatus, SessionResolution # --------------------------------------------------------------------------- # Helper factories @@ -232,6 +234,24 @@ async def test_resume_session_with_mcp_servers(self, monkeypatch) -> None: # that resume_session skips history injection into agent_loop. mock_storage_cls.repair_interrupted.return_value = [] monkeypatch.setattr("iac_code.acp.server.SessionStorage", mock_storage_cls) + monkeypatch.setattr( + "iac_code.acp.server.resolve_session_argument", + lambda index, cwd, arg: SessionResolution( + status=ResolutionStatus.FOUND, + entry=SessionEntry( + session_id="test-session", + cwd="/tmp", + project_name="-tmp", + git_branch=None, + title="test-session", + mtime=0.0, + size_bytes=0, + name=None, + auto_title=None, + is_legacy=False, + ), + ), + ) sse = _make_sse_server(name="resumed-sse") await server.resume_session(cwd="/tmp", session_id="test-session", mcp_servers=[sse]) diff --git a/tests/acp/test_scenarios.py b/tests/acp/test_scenarios.py index 96b8a5c..b81716a 100644 --- a/tests/acp/test_scenarios.py +++ b/tests/acp/test_scenarios.py @@ -1415,6 +1415,10 @@ async def test_pushed_commands_include_input_hint(monkeypatch: pytest.MonkeyPatc assert debug_cmd.input is not None assert debug_cmd.input.root.hint == "[on|off]" + rename_cmd = commands_by_name["rename"] + assert rename_cmd.input is not None + assert rename_cmd.input.root.hint == "" + # Only ACP-supported commands are pushed, model/effort are excluded assert "model" not in commands_by_name assert "effort" not in commands_by_name diff --git a/tests/acp/test_server_coverage.py b/tests/acp/test_server_coverage.py index c9b7c23..ea67f8a 100644 --- a/tests/acp/test_server_coverage.py +++ b/tests/acp/test_server_coverage.py @@ -32,6 +32,7 @@ ) from iac_code.acp.session import ACPSession from iac_code.agent.message import Message +from iac_code.services.session_storage import SessionStorage from iac_code.types.stream_events import MessageEndEvent, TextDeltaEvent, Usage # --------------------------------------------------------------------------- @@ -117,6 +118,24 @@ async def test_list_sessions_with_cwd_project_dir_exists(monkeypatch, tmp_path) assert resp.next_cursor is None +@pytest.mark.asyncio +async def test_list_sessions_with_cwd_includes_directory_sessions_and_names(monkeypatch, tmp_path) -> None: + """list_sessions with cwd includes directory-format sessions and uses metadata names as titles.""" + monkeypatch.setattr("iac_code.utils.project_paths.get_config_dir", lambda: tmp_path) + + storage = SessionStorage() + storage.save("/tmp", "named-dir-session", [Message(role="user", content="hello")]) + storage.rename_session("/tmp", "named-dir-session", "deploy-prod", git_branch="main") + + server = ACPServer() + resp = await server.list_sessions(cwd="/tmp") + + sessions_by_id = {session.session_id: session for session in resp.sessions} + assert "named-dir-session" in sessions_by_id + assert sessions_by_id["named-dir-session"].title == "deploy-prod" + assert sessions_by_id["named-dir-session"].cwd == "/tmp" + + @pytest.mark.asyncio async def test_list_sessions_with_cwd_project_dir_not_exists(monkeypatch, tmp_path) -> None: """list_sessions with cwd but project_dir does not exist → empty list.""" diff --git a/tests/acp/test_sessions.py b/tests/acp/test_sessions.py index 9dbd49d..19ce1cc 100644 --- a/tests/acp/test_sessions.py +++ b/tests/acp/test_sessions.py @@ -14,6 +14,8 @@ from iac_code.acp.server import SESSION_IDLE_TIMEOUT, ACPServer from iac_code.acp.session import ACPSession, _current_turn_id from iac_code.acp.state import TurnState +from iac_code.agent.message import Message, TextBlock +from iac_code.services.session_storage import SessionStorage from iac_code.types.stream_events import MessageEndEvent, TextDeltaEvent, Usage @@ -446,9 +448,10 @@ async def run_streaming(self, prompt: str): class _ResumeRuntime: - def __init__(self, session_id: str = "test-session") -> None: + def __init__(self, session_id: str = "test-session", cwd: str | None = None) -> None: self.session_id = session_id self.agent_loop = _ResumeLoop() + self.agent_loop._cwd = cwd self.tool_registry = None @@ -456,7 +459,7 @@ def _patch_resume_server(monkeypatch: pytest.MonkeyPatch, session_id: str = "tes monkeypatch.setattr("iac_code.acp.server.load_saved_model", lambda: "fake-model") monkeypatch.setattr( "iac_code.acp.server.create_agent_runtime", - lambda options: _ResumeRuntime(session_id=options.session_id or session_id), + lambda options: _ResumeRuntime(session_id=options.session_id or session_id, cwd=options.cwd), ) monkeypatch.setattr( "iac_code.acp.server.replace_bash_with_acp_terminal", @@ -480,6 +483,57 @@ async def test_resume_active_session_returns_immediately(monkeypatch: pytest.Mon assert sid in server.sessions +@pytest.mark.asyncio +async def test_resume_active_session_from_other_cwd_raises_hint(monkeypatch: pytest.MonkeyPatch) -> None: + """An in-memory active session follows the same project boundary as persisted sessions.""" + _patch_resume_server(monkeypatch) + conn = _RecordingFakeConn() + server = ACPServer() + server.on_connect(conn) + + resp = await server.new_session(cwd="/source project;unsafe") + sid = resp.session_id + + with pytest.raises(acp.RequestError) as exc_info: + await server.resume_session(cwd="/other", session_id=sid) + + assert isinstance(exc_info.value.data, dict) + assert exc_info.value.data["cwd"] == "/source project;unsafe" + assert exc_info.value.data["hint"] == "cd '/source project;unsafe' && iac-code --resume test-session" + assert sid in str(exc_info.value) + assert "cd '/source project;unsafe' && iac-code --resume test-session" in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_resume_resolved_name_rejects_active_session_from_other_cwd( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + """The post-resolution active-session fast path also enforces project ownership.""" + _patch_resume_server(monkeypatch, session_id="same-id") + monkeypatch.setattr("iac_code.utils.project_paths.get_config_dir", lambda: tmp_path) + + storage = SessionStorage() + storage.save( + "/current", + "same-id", + [Message(role="user", content=[TextBlock(text="hello")])], + ) + storage.rename_session("/current", "same-id", "deploy-prod", git_branch=None) + + conn = _RecordingFakeConn() + server = ACPServer() + server.on_connect(conn) + await server.new_session(cwd="/other project;unsafe") + + with pytest.raises(acp.RequestError) as exc_info: + await server.resume_session(cwd="/current", session_id="deploy-prod") + + assert isinstance(exc_info.value.data, dict) + assert exc_info.value.data["cwd"] == "/other project;unsafe" + assert exc_info.value.data["hint"] == "cd '/other project;unsafe' && iac-code --resume same-id" + assert "cd '/other project;unsafe' && iac-code --resume same-id" in str(exc_info.value) + + @pytest.mark.asyncio async def test_resume_nonexistent_session_raises_error(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: """Resuming a session that doesn't exist in memory or storage raises RequestError.""" @@ -519,6 +573,116 @@ async def test_resume_from_storage(monkeypatch: pytest.MonkeyPatch, tmp_path) -> assert len(ctx.loaded_messages) == 1 +@pytest.mark.asyncio +async def test_resume_session_accepts_name(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: + """Resuming by session name resolves to the persisted session id.""" + _patch_resume_server(monkeypatch) + monkeypatch.setattr("iac_code.utils.project_paths.get_config_dir", lambda: tmp_path) + + storage = SessionStorage() + storage.save( + "/tmp", + "stored-named-session", + [Message(role="user", content=[TextBlock(text="hello")])], + ) + storage.rename_session("/tmp", "stored-named-session", "deploy-prod", git_branch="main") + + conn = _RecordingFakeConn() + server = ACPServer() + server.on_connect(conn) + + result = await server.resume_session(cwd="/tmp", session_id="deploy-prod") + + assert isinstance(result, acp.schema.ResumeSessionResponse) + assert "deploy-prod" not in server.sessions + assert "stored-named-session" in server.sessions + resumed_session = server.sessions["stored-named-session"] + assert resumed_session.id == "stored-named-session" + ctx = resumed_session.agent_loop.context_manager + assert len(ctx.loaded_messages) == 1 + + +@pytest.mark.asyncio +async def test_resume_session_accepts_id_prefix(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: + """Resuming by unique session id prefix resolves to the full persisted session id.""" + _patch_resume_server(monkeypatch) + monkeypatch.setattr("iac_code.utils.project_paths.get_config_dir", lambda: tmp_path) + + storage = SessionStorage() + storage.save( + "/tmp", + "prefix-session-123", + [Message(role="user", content=[TextBlock(text="hello")])], + ) + + conn = _RecordingFakeConn() + server = ACPServer() + server.on_connect(conn) + + result = await server.resume_session(cwd="/tmp", session_id="prefix-session") + + assert isinstance(result, acp.schema.ResumeSessionResponse) + assert "prefix-session" not in server.sessions + assert "prefix-session-123" in server.sessions + + +@pytest.mark.asyncio +async def test_resume_session_single_cross_project_match_raises_hint(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: + """A single foreign-project match is rejected with a concrete resume command hint.""" + _patch_resume_server(monkeypatch) + monkeypatch.setattr("iac_code.utils.project_paths.get_config_dir", lambda: tmp_path) + + storage = SessionStorage() + foreign_cwd = "/other project;unsafe" + storage.save( + foreign_cwd, + "foreign-session-123", + [Message(role="user", content=[TextBlock(text="hello")])], + ) + storage.rename_session(foreign_cwd, "foreign-session-123", "foreign-deploy", git_branch=None) + + conn = _RecordingFakeConn() + server = ACPServer() + server.on_connect(conn) + + with pytest.raises(acp.RequestError) as exc_info: + await server.resume_session(cwd="/tmp", session_id="foreign-deploy") + + assert isinstance(exc_info.value.data, dict) + assert exc_info.value.data["hint"] == "cd '/other project;unsafe' && iac-code --resume foreign-session-123" + assert "foreign-session-123" in str(exc_info.value) + assert "cd '/other project;unsafe' && iac-code --resume foreign-session-123" in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_resume_session_ambiguous_name_raises_candidates(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: + """ACP resume reports candidate ids when a name exists in multiple projects.""" + _patch_resume_server(monkeypatch) + monkeypatch.setattr("iac_code.utils.project_paths.get_config_dir", lambda: tmp_path) + + storage = SessionStorage() + message = Message(role="user", content=[TextBlock(text="hello")]) + storage.save("/project a;bad", "candidate-a", [message]) + storage.rename_session("/project a;bad", "candidate-a", "deploy-prod", git_branch=None) + storage.save("/project-b", "candidate-b", [message]) + storage.rename_session("/project-b", "candidate-b", "deploy-prod", git_branch=None) + + conn = _RecordingFakeConn() + server = ACPServer() + server.on_connect(conn) + + with pytest.raises(acp.RequestError) as exc_info: + await server.resume_session(cwd="/current", session_id="deploy-prod") + + assert "candidate-a" in str(exc_info.value) + assert "candidate-b" in str(exc_info.value) + assert isinstance(exc_info.value.data, dict) + candidates = exc_info.value.data["candidates"] + commands_by_id = {candidate["session_id"]: candidate["command"] for candidate in candidates} + assert commands_by_id["candidate-a"] == "cd '/project a;bad' && iac-code --resume candidate-a" + assert commands_by_id["candidate-b"] == "cd /project-b && iac-code --resume candidate-b" + + @pytest.mark.asyncio async def test_resume_session_can_prompt_after_restore(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: """A resumed session can accept new prompts normally.""" diff --git a/tests/acp/test_slash_registry.py b/tests/acp/test_slash_registry.py index 4f14bc0..29e08d8 100644 --- a/tests/acp/test_slash_registry.py +++ b/tests/acp/test_slash_registry.py @@ -274,3 +274,78 @@ async def test_memory_help_missing_invalid_name_and_unknown_usage(registry: ACPS assert missing == "Memory 'missing' not found." assert invalid == "Invalid memory name: '../escape'" assert unknown == "Usage: /memory [|search |delete |help]" + + +# --------------------------------------------------------------------------- +# execute — /rename +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_rename_success_calls_storage_with_session_context(registry: ACPSlashRegistry) -> None: + agent_loop = MagicMock() + agent_loop._cwd = "/project" + agent_loop._session_id = "session-1" + agent_loop._current_git_branch = "main" + + storage = MagicMock() + storage.rename_session.return_value = "renamed" + + with patch("iac_code.acp.slash_registry.SessionStorage", return_value=storage): + result = await registry.execute("/rename deploy-prod", agent_loop=agent_loop) + + storage.rename_session.assert_called_once_with( + "/project", + "session-1", + "deploy-prod", + git_branch="main", + ) + assert result == "Renamed session to deploy-prod" + + +@pytest.mark.asyncio +async def test_rename_requires_name(registry: ACPSlashRegistry) -> None: + result = await registry.execute("/rename", agent_loop=MagicMock()) + + assert result == "Usage: /rename " + + +@pytest.mark.asyncio +async def test_rename_rejects_multi_token_name(registry: ACPSlashRegistry) -> None: + agent_loop = MagicMock() + + result = await registry.execute("/rename deploy prod", agent_loop=agent_loop) + + assert result == "Usage: /rename " + + +@pytest.mark.asyncio +async def test_rename_value_error_returns_message(registry: ACPSlashRegistry) -> None: + agent_loop = MagicMock() + agent_loop._cwd = "/project" + agent_loop._session_id = "session-1" + agent_loop._current_git_branch = None + + storage = MagicMock() + storage.rename_session.side_effect = ValueError("Session name already exists in this project: deploy-prod") + + with patch("iac_code.acp.slash_registry.SessionStorage", return_value=storage): + result = await registry.execute("/rename deploy-prod", agent_loop=agent_loop) + + assert result == "Session name already exists in this project: deploy-prod" + + +@pytest.mark.asyncio +async def test_rename_unchanged_message(registry: ACPSlashRegistry) -> None: + agent_loop = MagicMock() + agent_loop._cwd = "/project" + agent_loop._session_id = "session-1" + agent_loop._current_git_branch = None + + storage = MagicMock() + storage.rename_session.return_value = "unchanged" + + with patch("iac_code.acp.slash_registry.SessionStorage", return_value=storage): + result = await registry.execute("/rename deploy-prod", agent_loop=agent_loop) + + assert result == "Session is already named deploy-prod" diff --git a/tests/commands/test_clear.py b/tests/commands/test_clear.py index a2a7398..be947ca 100644 --- a/tests/commands/test_clear.py +++ b/tests/commands/test_clear.py @@ -56,8 +56,8 @@ async def test_clear_with_console_writes_ansi_and_banner(monkeypatch): calls = [] - def fake_banner(model, cwd): - calls.append((model, cwd)) + def fake_banner(model, cwd, *, session_id=None, session_name=None): + calls.append((model, cwd, session_id, session_name)) return "BANNER" monkeypatch.setattr("iac_code.ui.banner.render_welcome_banner", fake_banner) @@ -66,4 +66,30 @@ def fake_banner(model, cwd): # ANSI escape written console.file.write.assert_called() console.print.assert_called_with("BANNER") - assert calls == [("claude-sonnet-4-6", "/tmp")] + assert calls == [("claude-sonnet-4-6", "/tmp", None, None)] + + +@pytest.mark.asyncio +async def test_clear_banner_preserves_repl_session_identity(monkeypatch): + store = MagicMock() + state = MagicMock(model="claude-sonnet-4-6", cwd="/tmp") + store.get_state.return_value = state + + console = MagicMock() + console.file = MagicMock() + repl = MagicMock(_session_id="session-123", _session_name="deploy-prod") + context = MagicMock(store=store, console=console, repl=repl) + + calls = [] + + def fake_banner(model, cwd, *, session_id=None, session_name=None): + calls.append((model, cwd, session_id, session_name)) + return "BANNER" + + monkeypatch.setattr("iac_code.ui.banner.render_welcome_banner", fake_banner) + + result = await clear_command(context=context) + + assert result == "" + console.print.assert_called_with("BANNER") + assert calls == [("claude-sonnet-4-6", "/tmp", "session-123", "deploy-prod")] diff --git a/tests/commands/test_registry.py b/tests/commands/test_registry.py index 053a692..5924d86 100644 --- a/tests/commands/test_registry.py +++ b/tests/commands/test_registry.py @@ -214,11 +214,11 @@ def test_create_default_registry_returns_registry(self): registry = create_default_registry() assert isinstance(registry, CommandRegistry) - def test_create_default_registry_has_12_commands(self): - """Test create_default_registry has 12 commands.""" + def test_create_default_registry_has_13_commands(self): + """Test create_default_registry has 13 commands.""" registry = create_default_registry() all_cmds = registry.get_all() - assert len(all_cmds) == 12 + assert len(all_cmds) == 13 def test_create_default_registry_command_names(self): """Test create_default_registry has expected command names.""" @@ -238,8 +238,17 @@ def test_create_default_registry_command_names(self): "memory", "skills", "status", + "rename", } + def test_default_registry_includes_rename(self): + """Test rename command metadata.""" + registry = create_default_registry() + rename_cmd = registry.get("rename") + assert rename_cmd is not None + assert rename_cmd.arg_hint == "" + assert rename_cmd.history_mode == "session" + def test_help_command_has_alias(self): """Test help command has ? alias.""" registry = create_default_registry() diff --git a/tests/commands/test_rename.py b/tests/commands/test_rename.py new file mode 100644 index 0000000..d25ca98 --- /dev/null +++ b/tests/commands/test_rename.py @@ -0,0 +1,120 @@ +"""Tests for the /rename command.""" + +from __future__ import annotations + +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from iac_code.commands.rename import rename_command + + +@pytest.mark.asyncio +async def test_rename_inline_success() -> None: + repl = MagicMock() + repl.rename_current_session = MagicMock(return_value="renamed") + context = MagicMock(repl=repl) + + result = await rename_command(context=context, args=["deploy-prod"]) + + assert "deploy-prod" in result.message + assert "renamed" in result.message.lower() + assert result.is_error is False + assert result.refresh_banner is True + repl.rename_current_session.assert_called_once_with("deploy-prod") + + +@pytest.mark.asyncio +async def test_rename_multiple_args_returns_usage() -> None: + repl = MagicMock() + repl.rename_current_session = MagicMock() + context = MagicMock(repl=repl) + + result = await rename_command(context=context, args=["deploy", "prod"]) + + assert result.message == "Usage: /rename " + assert result.is_error is True + assert result.refresh_banner is False + repl.rename_current_session.assert_not_called() + + +@pytest.mark.asyncio +async def test_rename_invalid_name_returns_validation_message() -> None: + repl = MagicMock() + repl.rename_current_session = MagicMock() + context = MagicMock(repl=repl) + + result = await rename_command(context=context, args=["bad name"]) + + assert result.message == "Session name must match ^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$" + assert result.is_error is True + assert result.refresh_banner is False + repl.rename_current_session.assert_not_called() + + +@pytest.mark.asyncio +async def test_rename_duplicate_value_error_returns_message() -> None: + repl = MagicMock() + repl.rename_current_session = MagicMock(side_effect=ValueError("Session name already exists: deploy-prod")) + context = MagicMock(repl=repl) + + result = await rename_command(context=context, args=["deploy-prod"]) + + assert result.message == "Session name already exists: deploy-prod" + assert result.is_error is True + assert result.refresh_banner is False + + +@pytest.mark.asyncio +async def test_rename_unchanged_message() -> None: + repl = MagicMock() + repl.rename_current_session = MagicMock(return_value="unchanged") + context = MagicMock(repl=repl) + + result = await rename_command(context=context, args=["deploy-prod"]) + + assert "already" in result.message.lower() + assert "deploy-prod" in result.message + assert result.is_error is False + assert result.refresh_banner is False + + +@pytest.mark.asyncio +async def test_rename_interactive_success() -> None: + repl = MagicMock() + repl.prompt_for_session_name = AsyncMock(return_value=" deploy-prod ") + repl.rename_current_session = MagicMock(return_value="renamed") + context = MagicMock(repl=repl) + + result = await rename_command(context=context, args=[]) + + assert "deploy-prod" in result.message + assert "renamed" in result.message.lower() + assert result.is_error is False + assert result.refresh_banner is True + repl.prompt_for_session_name.assert_awaited_once() + repl.rename_current_session.assert_called_once_with("deploy-prod") + + +@pytest.mark.asyncio +async def test_rename_interactive_cancelled() -> None: + repl = MagicMock() + repl.prompt_for_session_name = AsyncMock(return_value=None) + repl.rename_current_session = MagicMock() + context = MagicMock(repl=repl) + + result = await rename_command(context=context, args=[]) + + assert "cancel" in result.message.lower() + assert result.is_error is False + assert result.refresh_banner is False + repl.rename_current_session.assert_not_called() + + +@pytest.mark.asyncio +async def test_rename_no_interactive_context_returns_message() -> None: + result = await rename_command(context=None, args=["deploy-prod"]) + + assert "interactive" in result.message.lower() + assert result.is_error is True + assert result.refresh_banner is False diff --git a/tests/commands/test_resume.py b/tests/commands/test_resume.py index 094e5b2..1e7f1bc 100644 --- a/tests/commands/test_resume.py +++ b/tests/commands/test_resume.py @@ -6,8 +6,10 @@ import pytest +import iac_code.commands.resume as resume_module from iac_code.commands.resume import resume_command from iac_code.services.session_index import SessionEntry +from iac_code.services.session_resolver import ResolutionStatus, SessionResolution def _entry(**overrides) -> SessionEntry: @@ -31,29 +33,124 @@ async def test_resume_no_context_returns_message(): @pytest.mark.asyncio -async def test_resume_with_id_swaps_when_found(): +async def test_resume_with_id_swaps_when_found(monkeypatch): entry = _entry() repl = MagicMock() - repl.session_index.find_by_id_or_prefix.return_value = entry + repl._original_cwd = "/proj/x" repl.swap_or_announce_session = AsyncMock() context = MagicMock(repl=repl) + monkeypatch.setattr( + resume_module, + "resolve_session_argument", + MagicMock(return_value=SessionResolution(status=ResolutionStatus.FOUND, entry=entry)), + raising=False, + ) result = await resume_command(context=context, args=["abc-1"]) assert result == "" - repl.session_index.find_by_id_or_prefix.assert_called_once_with("abc-1") repl.swap_or_announce_session.assert_awaited_once_with(entry) @pytest.mark.asyncio -async def test_resume_with_id_not_found_returns_error(): +async def test_resume_with_name_uses_resolver_and_swaps_when_found(monkeypatch): + entry = _entry(name="deploy-prod", title="deploy-prod") repl = MagicMock() - repl.session_index.find_by_id_or_prefix.return_value = None + repl._original_cwd = "/proj/x" + repl.swap_or_announce_session = AsyncMock() context = MagicMock(repl=repl) + resolve_session_argument = MagicMock( + return_value=SessionResolution(status=ResolutionStatus.FOUND, entry=entry), + ) + monkeypatch.setattr(resume_module, "resolve_session_argument", resolve_session_argument, raising=False) + + result = await resume_command(context=context, args=["deploy-prod"]) + + assert result == "" + resolve_session_argument.assert_called_once_with(repl.session_index, "/proj/x", "deploy-prod") + repl.session_index.find_by_id_or_prefix.assert_not_called() + repl.swap_or_announce_session.assert_awaited_once_with(entry) + + +@pytest.mark.asyncio +async def test_resume_with_id_not_found_returns_error(monkeypatch): + repl = MagicMock() + repl._original_cwd = "/proj/x" + context = MagicMock(repl=repl) + monkeypatch.setattr( + resume_module, + "resolve_session_argument", + MagicMock(return_value=SessionResolution(status=ResolutionStatus.NOT_FOUND)), + raising=False, + ) + result = await resume_command(context=context, args=["nope"]) assert "not found" in result.lower() + repl.session_index.find_by_id_or_prefix.assert_not_called() + + +@pytest.mark.asyncio +async def test_resume_with_ambiguous_name_opens_picker_with_candidates_and_swaps(monkeypatch): + selected = _entry(session_id="picked", name="deploy-prod", title="deploy-prod") + candidates = [ + selected, + _entry(session_id="other", cwd="/proj/y", project_name="y", name="deploy-prod", title="deploy-prod"), + ] + repl = MagicMock() + repl._original_cwd = "/proj/x" + repl.session_id = "current" + repl._keybinding_manager = object() + repl.renderer = object() + repl.swap_or_announce_session = AsyncMock() + context = MagicMock(repl=repl) + monkeypatch.setattr( + resume_module, + "resolve_session_argument", + MagicMock(return_value=SessionResolution(status=ResolutionStatus.AMBIGUOUS_NAME, candidates=candidates)), + raising=False, + ) + + fake_picker_instance = MagicMock() + fake_picker_instance.run.return_value = selected + fake_picker_cls = MagicMock(return_value=fake_picker_instance) + monkeypatch.setattr("iac_code.ui.dialogs.resume_picker.ResumePicker", fake_picker_cls) + + result = await resume_command(context=context, args=["deploy-prod"]) + + assert result == "" + fake_picker_cls.assert_called_once_with( + index=repl.session_index, + current_cwd="/proj/x", + current_session_id="current", + keybinding_manager=repl._keybinding_manager, + renderer=repl.renderer, + entries=candidates, + ) + repl.swap_or_announce_session.assert_awaited_once_with(selected) + + +@pytest.mark.asyncio +async def test_resume_with_unknown_resolution_status_returns_error(monkeypatch): + repl = MagicMock() + repl._original_cwd = "/proj/x" + repl.swap_or_announce_session = AsyncMock() + context = MagicMock(repl=repl) + resolution = MagicMock() + resolution.status = "future-status" + monkeypatch.setattr( + resume_module, + "resolve_session_argument", + MagicMock(return_value=resolution), + raising=False, + ) + + result = await resume_command(context=context, args=["deploy-prod"]) + + assert result + assert "unable" in result.lower() + repl.swap_or_announce_session.assert_not_awaited() @pytest.mark.asyncio diff --git a/tests/services/test_session_index.py b/tests/services/test_session_index.py index 47aab38..ee8f152 100644 --- a/tests/services/test_session_index.py +++ b/tests/services/test_session_index.py @@ -14,6 +14,7 @@ extract_last_json_string_field, read_lite_metadata, ) +from iac_code.services.session_metadata import SESSION_JSONL_FILENAME, SessionMetadata, write_session_metadata from iac_code.services.session_storage import SessionStorage from iac_code.services.session_usage import SessionUsageStore from iac_code.types.stream_events import Usage @@ -110,6 +111,77 @@ def test_list_all_projects_includes_everything(self, tmp_path): ids = {e.session_id for e in index.list_all_projects()} assert ids == {"id-a", "id-b"} + def test_list_all_projects_includes_legacy_sessions(self, tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + legacy_path = storage.legacy_session_path("/legacy", "legacy-id") + legacy_path.parent.mkdir(parents=True, exist_ok=True) + legacy_path.write_text('{"role":"user","content":"old","cwd":"/legacy"}\n', encoding="utf-8") + + index = SessionIndex(projects_dir=tmp_path) + entries = index.list_all_projects() + + assert [(e.session_id, e.cwd, e.title) for e in entries] == [("legacy-id", "/legacy", "old")] + + def test_directory_session_metadata_name_takes_precedence(self, tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + storage.append("/p", "named", Message(role="user", content="first prompt"), git_branch=None) + storage.rename_session("/p", "named", "deploy-prod", git_branch=None) + + entry = SessionIndex(projects_dir=tmp_path).list_for_cwd("/p")[0] + + assert entry.session_id == "named" + assert entry.name == "deploy-prod" + assert entry.title == "deploy-prod" + assert entry.auto_title == "first prompt" + assert entry.is_legacy is False + + def test_legacy_session_still_indexed(self, tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + legacy_path = storage.legacy_session_path("/legacy", "legacy") + legacy_path.parent.mkdir(parents=True, exist_ok=True) + legacy_path.write_text('{"role":"user","content":"old","cwd":"/legacy"}\n', encoding="utf-8") + + entry = SessionIndex(projects_dir=tmp_path).list_for_cwd("/legacy")[0] + + assert entry.session_id == "legacy" + assert entry.name is None + assert entry.title == "old" + assert entry.is_legacy is True + + def test_directory_session_ignores_stale_metadata_session_id(self, tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + storage.append("/p", "actual", Message(role="user", content="first prompt"), git_branch=None) + storage.rename_session("/p", "actual", "deploy-prod", git_branch=None) + write_session_metadata( + storage.session_dir("/p", "actual"), + SessionMetadata(session_id="stale", name="copied-name", cwd="/p", git_branch=None), + ) + + entry = SessionIndex(projects_dir=tmp_path).list_for_cwd("/p")[0] + + assert entry.session_id == "actual" + assert entry.name is None + assert entry.title == "first prompt" + assert entry.auto_title == "first prompt" + assert entry.is_legacy is False + + def test_duplicate_legacy_and_directory_session_id_prefers_directory(self, tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + legacy_path = storage.legacy_session_path("/p", "same") + legacy_path.parent.mkdir(parents=True, exist_ok=True) + legacy_path.write_text('{"role":"user","content":"legacy","cwd":"/p"}\n', encoding="utf-8") + + session_dir = storage.session_dir("/p", "same") + session_dir.mkdir(parents=True, exist_ok=True) + (session_dir / SESSION_JSONL_FILENAME).write_text( + '{"role":"user","content":"directory","cwd":"/p"}\n', + encoding="utf-8", + ) + + entries = SessionIndex(projects_dir=tmp_path).list_for_cwd("/p") + + assert [(entry.session_id, entry.title, entry.is_legacy) for entry in entries] == [("same", "directory", False)] + def test_list_sorted_by_mtime_desc(self, tmp_path): storage = SessionStorage(projects_dir=tmp_path) storage.append("/p", "older", Message(role="user", content="o"), git_branch=None) diff --git a/tests/services/test_session_metadata.py b/tests/services/test_session_metadata.py new file mode 100644 index 0000000..3fc3827 --- /dev/null +++ b/tests/services/test_session_metadata.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import json + +import pytest + +from iac_code.services.session_metadata import ( + SESSION_METADATA_FILENAME, + SESSION_NAME_PATTERN, + SessionMetadata, + normalize_session_name, + read_session_metadata, + validate_session_name, + write_session_metadata, +) + + +@pytest.mark.parametrize("name", ["deploy", "deploy-prod", "prod_1", "release.2026", "A" * 200]) +def test_validate_session_name_accepts_slug_names(name: str) -> None: + assert validate_session_name(name) == name + + +@pytest.mark.parametrize("name", ["", " ", "deploy prod", "中文", "-bad", ".bad", "_bad", "A" * 201]) +def test_validate_session_name_rejects_invalid_names(name: str) -> None: + with pytest.raises(ValueError): + validate_session_name(name) + + +def test_normalize_session_name_strips_then_validates() -> None: + assert normalize_session_name(" deploy-prod ") == "deploy-prod" + + +def test_session_name_pattern_is_exported() -> None: + assert SESSION_NAME_PATTERN.pattern == r"^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$" + + +@pytest.mark.parametrize("schema_version", ["1", {}]) +def test_metadata_from_dict_defaults_non_int_schema_version(schema_version: object) -> None: + metadata = SessionMetadata.from_dict({"session_id": "abc123", "schema_version": schema_version}) + + assert metadata is not None + assert metadata.schema_version == 1 + + +def test_metadata_from_dict_defaults_bool_schema_version() -> None: + metadata = SessionMetadata.from_dict({"session_id": "abc123", "schema_version": True}) + + assert metadata is not None + assert type(metadata.schema_version) is int + assert metadata.schema_version == 1 + + +def test_metadata_round_trip(tmp_path) -> None: + session_dir = tmp_path / "abc123" + metadata = SessionMetadata( + session_id="abc123", + name="deploy-prod", + cwd="/project", + git_branch="main", + created_at="2026-06-02T12:00:00Z", + updated_at="2026-06-02T12:01:00Z", + ) + + write_session_metadata(session_dir, metadata) + + raw = json.loads((session_dir / SESSION_METADATA_FILENAME).read_text(encoding="utf-8")) + assert raw["schema_version"] == 1 + assert raw["name"] == "deploy-prod" + assert read_session_metadata(session_dir) == metadata + + +def test_read_session_metadata_ignores_corrupt_json(tmp_path) -> None: + session_dir = tmp_path / "abc123" + session_dir.mkdir() + (session_dir / SESSION_METADATA_FILENAME).write_text("{not-json", encoding="utf-8") + + assert read_session_metadata(session_dir) is None diff --git a/tests/services/test_session_resolver.py b/tests/services/test_session_resolver.py new file mode 100644 index 0000000..499eebb --- /dev/null +++ b/tests/services/test_session_resolver.py @@ -0,0 +1,74 @@ +"""Tests for shared session argument resolution.""" + +from __future__ import annotations + +from iac_code.agent.message import Message +from iac_code.services.session_index import SessionIndex +from iac_code.services.session_resolver import ResolutionStatus, resolve_session_argument +from iac_code.services.session_storage import SessionStorage + + +def test_resolves_current_project_name(tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + storage.append("/p", "session-a", Message(role="user", content="hello"), git_branch=None) + storage.rename_session("/p", "session-a", "deploy-prod", git_branch=None) + + result = resolve_session_argument(SessionIndex(projects_dir=tmp_path), "/p", "deploy-prod") + + assert result.status is ResolutionStatus.FOUND + assert result.entry is not None + assert result.entry.session_id == "session-a" + + +def test_current_project_id_wins_over_cross_project_name(tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + storage.append("/current", "deploy-prod", Message(role="user", content="current id"), git_branch=None) + storage.append("/other", "other-id", Message(role="user", content="other name"), git_branch=None) + storage.rename_session("/other", "other-id", "deploy-prod", git_branch=None) + + result = resolve_session_argument(SessionIndex(projects_dir=tmp_path), "/current", "deploy-prod") + + assert result.status is ResolutionStatus.FOUND + assert result.entry is not None + assert result.entry.session_id == "deploy-prod" + assert result.entry.cwd == "/current" + + +def test_cross_project_duplicate_name_is_ambiguous(tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + storage.append("/a", "session-a", Message(role="user", content="a"), git_branch=None) + storage.append("/b", "session-b", Message(role="user", content="b"), git_branch=None) + storage.rename_session("/a", "session-a", "deploy-prod", git_branch=None) + storage.rename_session("/b", "session-b", "deploy-prod", git_branch=None) + + result = resolve_session_argument(SessionIndex(projects_dir=tmp_path), "/c", "deploy-prod") + + assert result.status is ResolutionStatus.AMBIGUOUS_NAME + assert result.entry is None + assert {entry.session_id for entry in result.candidates} == {"session-a", "session-b"} + + +def test_ambiguous_id_prefix_is_not_found(tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + storage.append("/p", "abc-one", Message(role="user", content="one"), git_branch=None) + storage.append("/p", "abc-two", Message(role="user", content="two"), git_branch=None) + + result = resolve_session_argument(SessionIndex(projects_dir=tmp_path), "/p", "abc") + + assert result.status is ResolutionStatus.NOT_FOUND + assert result.entry is None + assert result.candidates == [] + + +def test_ambiguous_id_prefix_is_not_resolved_as_name(tmp_path): + storage = SessionStorage(projects_dir=tmp_path) + storage.append("/p", "abc-one", Message(role="user", content="one"), git_branch=None) + storage.append("/p", "abc-two", Message(role="user", content="two"), git_branch=None) + storage.append("/p", "named", Message(role="user", content="named"), git_branch=None) + storage.rename_session("/p", "named", "abc", git_branch=None) + + result = resolve_session_argument(SessionIndex(projects_dir=tmp_path), "/p", "abc") + + assert result.status is ResolutionStatus.NOT_FOUND + assert result.entry is None + assert result.candidates == [] diff --git a/tests/services/test_session_storage.py b/tests/services/test_session_storage.py index 82c025d..a698026 100644 --- a/tests/services/test_session_storage.py +++ b/tests/services/test_session_storage.py @@ -4,6 +4,7 @@ import pytest from iac_code.agent.message import Message, TextBlock, ToolResultBlock, ToolUseBlock +from iac_code.services.session_metadata import SESSION_JSONL_FILENAME, SESSION_METADATA_FILENAME from iac_code.services.session_storage import SessionStorage from iac_code.services.session_usage import SessionUsageStore from iac_code.types.stream_events import Usage @@ -105,7 +106,7 @@ def test_find_session_anywhere(self, storage): assert result is not None cwd, path = result assert cwd == "/tmp/b" - assert path.name == "id-bb.jsonl" + assert path.name == SESSION_JSONL_FILENAME assert storage.find_session_anywhere("missing") is None def test_get_latest_session_anywhere(self, storage): @@ -155,3 +156,67 @@ def test_repair_interrupted_inserts_synthetic_results(self, storage): repaired = SessionStorage.repair_interrupted(loaded) assert repaired[-1].role == "user" assert any(getattr(b, "is_error", False) for b in repaired[-1].content) + + +def test_new_session_uses_directory_format(storage): + storage.append(CWD, "dir-session", Message(role="user", content="hi"), git_branch="main") + + legacy_path = storage.legacy_session_path(CWD, "dir-session") + session_dir = storage.session_dir(CWD, "dir-session") + + assert session_dir.is_dir() + assert (session_dir / SESSION_JSONL_FILENAME).exists() + assert not legacy_path.exists() + assert storage.load(CWD, "dir-session") == [Message(role="user", content="hi")] + + +def test_existing_legacy_session_stays_legacy_until_rename(storage): + legacy_path = storage.legacy_session_path(CWD, "legacy") + legacy_path.parent.mkdir(parents=True, exist_ok=True) + legacy_path.write_text('{"role":"user","content":"old"}\n', encoding="utf-8") + + storage.append(CWD, "legacy", Message(role="assistant", content="next"), git_branch=None) + + assert legacy_path.exists() + assert not storage.session_dir(CWD, "legacy").exists() + assert [m.role for m in storage.load(CWD, "legacy")] == ["user", "assistant"] + + +def test_rename_legacy_session_migrates_to_directory(storage): + legacy_path = storage.legacy_session_path(CWD, "legacy-rename") + legacy_path.parent.mkdir(parents=True, exist_ok=True) + legacy_path.write_text('{"role":"user","content":"old"}\n', encoding="utf-8") + + result = storage.rename_session(CWD, "legacy-rename", "deploy-prod", git_branch="main") + + session_dir = storage.session_dir(CWD, "legacy-rename") + assert result == "renamed" + assert not legacy_path.exists() + assert (session_dir / SESSION_JSONL_FILENAME).exists() + assert (session_dir / SESSION_METADATA_FILENAME).exists() + assert storage.read_metadata(CWD, "legacy-rename").name == "deploy-prod" + assert storage.load(CWD, "legacy-rename")[0].content == "old" + + +def test_rename_rejects_same_project_duplicate_name(storage): + storage.append(CWD, "one", Message(role="user", content="one"), git_branch=None) + storage.append(CWD, "two", Message(role="user", content="two"), git_branch=None) + storage.rename_session(CWD, "one", "deploy-prod", git_branch=None) + + with pytest.raises(ValueError, match="already exists"): + storage.rename_session(CWD, "two", "deploy-prod", git_branch=None) + + +def test_rename_allows_same_name_in_different_projects(storage): + storage.append("/p1", "one", Message(role="user", content="one"), git_branch=None) + storage.append("/p2", "two", Message(role="user", content="two"), git_branch=None) + + assert storage.rename_session("/p1", "one", "deploy-prod", git_branch=None) == "renamed" + assert storage.rename_session("/p2", "two", "deploy-prod", git_branch=None) == "renamed" + + +def test_rename_to_existing_name_is_noop(storage): + storage.append(CWD, "same", Message(role="user", content="one"), git_branch=None) + storage.rename_session(CWD, "same", "deploy-prod", git_branch=None) + + assert storage.rename_session(CWD, "same", "deploy-prod", git_branch=None) == "unchanged" diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 4287fa4..42afa8f 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -352,6 +352,28 @@ def test_aliyun_credential_labels_are_translatable(): ) +@pytest.mark.skipif(sys.platform == "win32", reason="messages.pot not generated on Windows") +def test_session_name_error_messages_are_translated(): + """Session rename validation errors are user-facing and must not stay English-only.""" + required_msgids = { + "Session name must match {pattern}", + "Session name already exists in this project: {name}", + } + language_dirs = _discover_language_dirs() + if not language_dirs: + pytest.skip("No language directories found") + + untranslated: list[str] = [] + for lang_dir in language_dirs: + translations = _get_all_translations_from_po(lang_dir / "LC_MESSAGES" / "messages.po") + for msgid in sorted(required_msgids): + msgstr = translations.get(msgid, "") + if not msgstr.strip() or msgstr == msgid: + untranslated.append(f"{lang_dir.name}: {msgid!r}") + + assert not untranslated + + class TestDetectWindowsUILanguage: """_detect_windows_ui_language wraps GetUserDefaultLocaleName via ctypes.""" diff --git a/tests/ui/dialogs/test_resume_picker.py b/tests/ui/dialogs/test_resume_picker.py index b02f47e..c1249e0 100644 --- a/tests/ui/dialogs/test_resume_picker.py +++ b/tests/ui/dialogs/test_resume_picker.py @@ -4,13 +4,13 @@ import io import time -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest from rich.console import Console as RichConsole from iac_code.agent.message import Message -from iac_code.services.session_index import SessionIndex +from iac_code.services.session_index import SessionEntry, SessionIndex from iac_code.services.session_storage import SessionStorage from iac_code.ui.core.key_event import KeyEvent from iac_code.ui.dialogs.resume_picker import ( @@ -52,6 +52,23 @@ def _make_renderer(): return Renderer(scratch, registry) +def _entry(**overrides) -> SessionEntry: + defaults = dict( + session_id="1234567890abcdef", + cwd="/proj/a", + project_name="a", + git_branch="main", + title="deploy-prod", + mtime=time.time(), + size_bytes=42, + name=None, + auto_title="create vpc resources", + is_legacy=False, + ) + defaults.update(overrides) + return SessionEntry(**defaults) + + class TestResumePickerLoad: def test_default_view_is_current_cwd(self, picker): ids = [e.session_id for e in picker._all_entries] @@ -70,6 +87,43 @@ def test_excludes_current_session(self, two_session_index): ids = [e.session_id for e in p._all_entries] assert ids == ["id-aa"] + def test_supplied_entries_are_used_without_index_reload(self): + index = MagicMock() + index.list_for_cwd = MagicMock(side_effect=AssertionError("should not reload from index")) + index.list_all_projects = MagicMock(side_effect=AssertionError("should not reload from index")) + entries = [ + _entry(session_id="candidate-a"), + _entry(session_id="current-session"), + _entry(session_id="candidate-b"), + ] + + with patch("iac_code.ui.dialogs.resume_picker.get_git_branch", return_value=None): + p = ResumePicker( + index=index, + current_cwd="/proj/a", + current_session_id="current-session", + entries=entries, + ) + + assert [entry.session_id for entry in p._all_entries] == ["candidate-a", "candidate-b"] + + def test_supplied_entries_are_not_reloaded_when_toggling_all_projects(self): + index = MagicMock() + index.list_for_cwd = MagicMock(side_effect=AssertionError("should not reload from index")) + index.list_all_projects = MagicMock(side_effect=AssertionError("should not reload from index")) + entries = [_entry(session_id="candidate-a")] + + with patch("iac_code.ui.dialogs.resume_picker.get_git_branch", return_value=None): + p = ResumePicker( + index=index, + current_cwd="/proj/a", + current_session_id=None, + entries=entries, + ) + p.handle_key(KeyEvent(key="a", char="\x01", ctrl=True)) + + assert [entry.session_id for entry in p._all_entries] == ["candidate-a"] + class TestResumePickerKeys: def test_escape_cancels(self, picker): @@ -193,6 +247,29 @@ def test_typing_filters_entries(self, picker): ids = [e.session_id for e in picker._filtered] assert ids == ["id-aa"] + def test_search_matches_name_session_id_auto_title_project_and_branch(self, two_session_index): + entries = [ + _entry( + session_id="session-by-id", + project_name="networking", + git_branch="feature-branch", + title="title text", + name="named-session", + auto_title="auto title text", + ) + ] + for query in ("named", "session-by-id", "title", "auto", "networking", "feature"): + with patch("iac_code.ui.dialogs.resume_picker.get_git_branch", return_value=None): + p = ResumePicker( + index=two_session_index, + current_cwd="/proj/a", + current_session_id=None, + entries=entries, + ) + for ch in query: + p.handle_key(KeyEvent(key=ch, char=ch)) + assert [entry.session_id for entry in p._filtered] == ["session-by-id"] + def test_down_arrow_moves_focus(self, picker): assert len(picker._filtered) >= 2 starting = picker._focused_index @@ -216,6 +293,13 @@ def test_render_when_empty(self, two_session_index): ) p.render() + def test_named_subtitle_includes_short_session_id(self): + entry = _entry(session_id="1234567890abcdef", name="deploy-prod", title="deploy-prod") + subtitle = ResumePicker._render_subtitle_line(entry).plain + + assert "12345678" in subtitle + assert "123456789" not in subtitle + class TestResumePickerPreviewDraw: def _picker_with_console(self, two_session_index, *, height=40, width=80): diff --git a/tests/ui/test_banner.py b/tests/ui/test_banner.py index d5dcb6b..cdffec5 100644 --- a/tests/ui/test_banner.py +++ b/tests/ui/test_banner.py @@ -183,10 +183,10 @@ def test_qwenpaw_source_no_config_fallback(self): class TestRenderWelcomeBanner: """Tests for render_welcome_banner(model, cwd).""" - def _call(self, model: str, cwd: str) -> Panel: + def _call(self, model: str, cwd: str, session_id: str | None = None, session_name: str | None = None) -> Panel: from iac_code.ui.banner import render_welcome_banner - return render_welcome_banner(model, cwd) + return render_welcome_banner(model, cwd, session_id=session_id, session_name=session_name) # ------------------------------------------------------------------ # Return-type tests @@ -196,6 +196,13 @@ def test_returns_panel(self, tmp_path): result = self._call("claude-3-5-sonnet", str(tmp_path)) assert isinstance(result, Panel) + def test_banner_shows_named_session(self, tmp_path): + panel = self._call("some-model", str(tmp_path), session_id="abc123", session_name="deploy-prod") + text = render_to_str(panel) + assert "Session" in text + assert "deploy-prod" in text + assert "abc123" in text + # ------------------------------------------------------------------ # cwd display: inside HOME → ~/... prefix # ------------------------------------------------------------------ diff --git a/tests/ui/test_repl_integration.py b/tests/ui/test_repl_integration.py index 624d964..1e04787 100644 --- a/tests/ui/test_repl_integration.py +++ b/tests/ui/test_repl_integration.py @@ -5,6 +5,7 @@ import re import subprocess import sys +from pathlib import Path from types import SimpleNamespace from unittest.mock import AsyncMock, Mock, patch @@ -37,6 +38,22 @@ def make_pending_update() -> PendingUpdate: ) +def make_session_entry(session_id: str, cwd: str, name: str | None = None): + from iac_code.services.session_index import SessionEntry + + return SessionEntry( + session_id=session_id, + cwd=cwd, + project_name="repo", + git_branch=None, + title=name or session_id, + mtime=123.0, + size_bytes=456, + name=name, + is_legacy=False, + ) + + class TestREPLProviderIntegration: @patch("iac_code.ui.repl.ProviderManager") @patch("iac_code.ui.repl.SessionStorage") @@ -273,17 +290,137 @@ def test_refresh_skills_updates_agent_loop_auto_trigger_skills(mock_mm, mock_ss, assert all(command.name != "project-skill" for command in repl._agent_loop._auto_trigger_skills) +def test_repl_rename_current_session_updates_storage_and_name(): + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._original_cwd = "/repo" + repl._session_id = "session-123" + repl._session_storage = Mock() + repl.current_git_branch = Mock(return_value="main") + repl._load_current_session_name = Mock(return_value="deploy-prod") + + result = repl.rename_current_session("deploy-prod") + + assert result == repl._session_storage.rename_session.return_value + repl._session_storage.rename_session.assert_called_once_with( + "/repo", + "session-123", + "deploy-prod", + git_branch="main", + ) + repl._load_current_session_name.assert_called_once_with() + assert repl._session_name == "deploy-prod" + + +def test_swap_session_refreshes_session_name_and_renders_banner(): + from iac_code.state.app_state import AppState + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._original_cwd = "/repo" + repl._session_id = "old-session" + repl._session_storage = SimpleNamespace( + load=Mock(return_value=[]), + repair_interrupted=Mock(return_value=[]), + ) + repl._agent_loop = SimpleNamespace(replace_session=Mock()) + repl._load_current_session_name = Mock(return_value="deploy-prod") + repl.store = SimpleNamespace(get_state=Mock(return_value=AppState(model="test-model", cwd="/repo"))) + repl.console = SimpleNamespace(file=SimpleNamespace(write=Mock(), flush=Mock()), print=Mock()) + repl.renderer = SimpleNamespace(replay_history=Mock()) + + with patch("iac_code.ui.repl.render_welcome_banner", return_value="banner") as render_welcome_banner: + repl.swap_session("new-session") + + assert repl._session_name == "deploy-prod" + repl._load_current_session_name.assert_called_once_with() + render_welcome_banner.assert_called_once_with( + "test-model", + "/repo", + session_id="new-session", + session_name="deploy-prod", + ) + repl.console.print.assert_called_once_with("banner") + + +def test_print_exit_text_uses_session_name_and_prints_session_id(): + from rich.text import Text + + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._session_id = "abc123" + repl._session_name = "deploy-prod" + repl.console = SimpleNamespace(print=Mock()) + + repl._print_exit_text() + + printed = [call.args[0] for call in repl.console.print.call_args_list] + assert "[dim]Goodbye![/dim]" in printed + assert any(isinstance(item, Text) and "iac-code --resume deploy-prod" in item.plain for item in printed) + assert any(isinstance(item, Text) and "Session ID: abc123" in item.plain for item in printed) + + +@pytest.mark.asyncio +async def test_prompt_for_session_name_retries_until_valid(): + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._prompt_input = SimpleNamespace(get_input=AsyncMock(side_effect=[" ", "bad name", "deploy-prod"])) + repl.renderer = SimpleNamespace(print_system_message=Mock()) + + result = await repl.prompt_for_session_name() + + assert result == "deploy-prod" + assert repl._prompt_input.get_input.await_count == 3 + assert repl.renderer.print_system_message.call_count == 2 + styles = [call.kwargs["style"] for call in repl.renderer.print_system_message.call_args_list] + assert styles == ["red", "red"] + + +def test_resolve_session_id_continue_returns_latest_current_project_session(): + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._original_cwd = "/repo" + repl._session_storage = SimpleNamespace(get_latest_session_anywhere=Mock(return_value=("/repo", "latest-id"))) + + assert repl._resolve_session_id(True) == "latest-id" + repl._session_storage.get_latest_session_anywhere.assert_called_once_with() + + +def test_resolve_session_id_continue_cross_project_raises_with_hint(): + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._original_cwd = "/repo" + repl._session_storage = SimpleNamespace( + get_latest_session_anywhere=Mock(return_value=("/elsewhere/repo", "latest-id")) + ) + + with pytest.raises(ValueError, match=r"cd /elsewhere/repo && iac-code --resume latest-id"): + repl._resolve_session_id(True) + + @patch("iac_code.ui.repl.ProviderManager") @patch("iac_code.ui.repl.SessionStorage") @patch("iac_code.ui.repl.MemoryManager") def test_resume_str_accepted_when_session_exists(mock_mm, mock_ss, mock_pm): + from iac_code.services.session_resolver import ResolutionStatus, SessionResolution from iac_code.ui.repl import InlineREPL existing_id = "99646984-35a9-4850-b72a-4131a1690774" - mock_ss.return_value.exists.return_value = True mock_ss.return_value.load.return_value = [] mock_ss.return_value.repair_interrupted.return_value = [] - repl = InlineREPL(model="test-model", resume_session_id=existing_id) + with patch( + "iac_code.ui.repl.resolve_session_argument", + return_value=SessionResolution( + status=ResolutionStatus.FOUND, + entry=make_session_entry(existing_id, str(Path.cwd())), + ), + ): + repl = InlineREPL(model="test-model", resume_session_id=existing_id) assert repl.session_id == existing_id @@ -291,32 +428,206 @@ def test_resume_str_accepted_when_session_exists(mock_mm, mock_ss, mock_pm): @patch("iac_code.ui.repl.SessionStorage") @patch("iac_code.ui.repl.MemoryManager") def test_resume_str_raises_when_session_missing(mock_mm, mock_ss, mock_pm): + from iac_code.services.session_resolver import ResolutionStatus, SessionResolution from iac_code.ui.repl import InlineREPL - mock_ss.return_value.exists.return_value = False - mock_ss.return_value.find_session_anywhere.return_value = None - import pytest - - with pytest.raises(ValueError, match="Session not found"): + with ( + patch( + "iac_code.ui.repl.resolve_session_argument", + return_value=SessionResolution(status=ResolutionStatus.NOT_FOUND), + ), + pytest.raises(ValueError, match="Session not found"), + ): InlineREPL(model="test-model", resume_session_id="no-such-id") @patch("iac_code.ui.repl.ProviderManager") @patch("iac_code.ui.repl.SessionStorage") @patch("iac_code.ui.repl.MemoryManager") -def test_resume_str_cross_project_raises_with_hint(mock_mm, mock_ss, mock_pm, tmp_path): +def test_resume_str_cross_project_raises_with_hint(mock_mm, mock_ss, mock_pm): """A resume id resolved in a different project must surface the cd command.""" + from iac_code.services.session_resolver import ResolutionStatus, SessionResolution + from iac_code.ui.repl import InlineREPL + + with ( + patch( + "iac_code.ui.repl.resolve_session_argument", + return_value=SessionResolution( + status=ResolutionStatus.FOUND, + entry=make_session_entry("some-id", "/elsewhere/repo"), + ), + ), + pytest.raises(ValueError, match=r"cd /elsewhere/repo && iac-code --resume some-id"), + ): + InlineREPL(model="test-model", resume_session_id="some-id") + + +def test_resolve_session_id_accepts_current_project_name(): + from iac_code.services.session_resolver import ResolutionStatus, SessionResolution + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._original_cwd = "/repo" + repl.session_index = object() + + with patch( + "iac_code.ui.repl.resolve_session_argument", + return_value=SessionResolution( + status=ResolutionStatus.FOUND, + entry=make_session_entry("abc123", repl._original_cwd, name="deploy-prod"), + ), + ) as resolve_session_argument: + result = repl._resolve_session_id("deploy-prod") + + assert result == "abc123" + resolve_session_argument.assert_called_once_with(repl.session_index, repl._original_cwd, "deploy-prod") + + +def test_resolve_session_id_ambiguous_name_raises_candidates(): + from iac_code.services.session_resolver import ResolutionStatus, SessionResolution + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._original_cwd = "/repo" + repl.session_index = object() + candidates = [ + make_session_entry("abc123", "/repo", name="deploy-prod"), + make_session_entry("def456", "/elsewhere/repo", name="deploy-prod"), + ] + + with ( + patch( + "iac_code.ui.repl.resolve_session_argument", + return_value=SessionResolution(status=ResolutionStatus.AMBIGUOUS_NAME, candidates=candidates), + ), + pytest.raises(ValueError) as exc_info, + ): + repl._resolve_session_id("deploy-prod") + + message = str(exc_info.value) + assert "Multiple sessions match" in message + assert "abc123" in message + assert "def456" in message + assert "cd /repo && iac-code --resume abc123" in message + assert "cd /elsewhere/repo && iac-code --resume def456" in message + + +def test_printed_session_name_resume_command_resolves_to_session_id(): + from rich.text import Text + + from iac_code.services.session_resolver import ResolutionStatus, SessionResolution from iac_code.ui.repl import InlineREPL - mock_ss.return_value.exists.return_value = False - mock_ss.return_value.find_session_anywhere.return_value = ( - "/elsewhere/repo", - tmp_path / "fake.jsonl", + repl = InlineREPL.__new__(InlineREPL) + repl._original_cwd = "/repo" + repl._session_id = "abc123" + repl._session_name = "deploy-prod" + repl.session_index = object() + repl.console = SimpleNamespace(print=Mock()) + + repl._print_exit_text() + command = next( + item.plain + for call in repl.console.print.call_args_list + for item in call.args + if isinstance(item, Text) and item.plain.startswith("iac-code --resume ") ) - import pytest + resume_arg = command.rsplit(" ", 1)[-1] + + with patch( + "iac_code.ui.repl.resolve_session_argument", + return_value=SessionResolution( + status=ResolutionStatus.FOUND, + entry=make_session_entry("abc123", repl._original_cwd, name="deploy-prod"), + ), + ): + assert repl._resolve_session_id(resume_arg) == "abc123" - with pytest.raises(ValueError, match=r"cd /elsewhere/repo && iac-code --resume"): - InlineREPL(model="test-model", resume_session_id="some-id") + +@pytest.mark.asyncio +async def test_rename_error_result_prints_red_and_records_error(): + from iac_code.commands.registry import LocalCommand + from iac_code.commands.rename import rename_command + from iac_code.state.app_state import AppState + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl.command_registry = SimpleNamespace( + parse=Mock(return_value=("rename", ["-bad"])), + get=Mock(return_value=LocalCommand(name="rename", description="Rename", handler=rename_command)), + ) + repl.renderer = SimpleNamespace(print_system_message=Mock(), print_command_result=Mock()) + repl.console = SimpleNamespace() + repl._agent_loop = SimpleNamespace(context_manager=SimpleNamespace(get_messages=Mock(return_value=[]))) + repl._command_log = [] + repl.store = SimpleNamespace(get_state=Mock(return_value=AppState(model="test-model", cwd="/repo"))) + repl._refresh_banner = Mock() + repl.rename_current_session = Mock() + + await repl._handle_command("/rename -bad") + + repl.renderer.print_system_message.assert_called_once() + assert repl.renderer.print_system_message.call_args.kwargs["style"] == "red" + repl.renderer.print_command_result.assert_not_called() + assert repl._command_log[-1][0] == "/rename -bad" + assert repl._command_log[-1][3] is True + repl._refresh_banner.assert_not_called() + + +@pytest.mark.asyncio +async def test_rename_success_refreshes_banner(): + from iac_code.commands.registry import LocalCommand + from iac_code.commands.rename import rename_command + from iac_code.state.app_state import AppState + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl.command_registry = SimpleNamespace( + parse=Mock(return_value=("rename", ["deploy-prod"])), + get=Mock(return_value=LocalCommand(name="rename", description="Rename", handler=rename_command)), + ) + repl.renderer = SimpleNamespace(print_system_message=Mock(), print_command_result=Mock()) + repl.console = SimpleNamespace() + repl._agent_loop = SimpleNamespace(context_manager=SimpleNamespace(get_messages=Mock(return_value=[]))) + repl._command_log = [] + repl.store = SimpleNamespace(get_state=Mock(return_value=AppState(model="test-model", cwd="/repo"))) + repl._refresh_banner = Mock() + repl.rename_current_session = Mock(return_value="renamed") + + await repl._handle_command("/rename deploy-prod") + + repl._refresh_banner.assert_called_once_with() + repl.renderer.print_command_result.assert_not_called() + assert repl._command_log[-1][0] == "/rename deploy-prod" + assert repl._command_log[-1][3] is False + + +@pytest.mark.asyncio +async def test_rename_unchanged_does_not_refresh_banner(): + from iac_code.commands.registry import LocalCommand + from iac_code.commands.rename import rename_command + from iac_code.state.app_state import AppState + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl.command_registry = SimpleNamespace( + parse=Mock(return_value=("rename", ["deploy-prod"])), + get=Mock(return_value=LocalCommand(name="rename", description="Rename", handler=rename_command)), + ) + repl.renderer = SimpleNamespace(print_system_message=Mock(), print_command_result=Mock()) + repl.console = SimpleNamespace() + repl._agent_loop = SimpleNamespace(context_manager=SimpleNamespace(get_messages=Mock(return_value=[]))) + repl._command_log = [] + repl.store = SimpleNamespace(get_state=Mock(return_value=AppState(model="test-model", cwd="/repo"))) + repl._refresh_banner = Mock() + repl.rename_current_session = Mock(return_value="unchanged") + + await repl._handle_command("/rename deploy-prod") + + repl._refresh_banner.assert_not_called() + repl.renderer.print_command_result.assert_called_once() + assert repl._command_log[-1][0] == "/rename deploy-prod" + assert repl._command_log[-1][3] is False @patch("iac_code.ui.repl.ProviderManager") diff --git a/tests/ui/test_repl_shell_escape.py b/tests/ui/test_repl_shell_escape.py index cc1cec4..150f0e7 100644 --- a/tests/ui/test_repl_shell_escape.py +++ b/tests/ui/test_repl_shell_escape.py @@ -16,6 +16,8 @@ class FakeRenderer: def __init__(self, permission_allowed: bool = True) -> None: self.messages: list[tuple[str, str]] = [] self.recorded_turns: list[str] = [] + self.user_messages: list[str] = [] + self.command_results: list[tuple[str, str]] = [] self.permission_allowed = permission_allowed self.permission_events = [] @@ -25,6 +27,12 @@ def print_system_message(self, text: str, style: str = "yellow") -> None: def record_user_turn(self, text: str) -> None: self.recorded_turns.append(text) + def print_user_message(self, text: str) -> None: + self.user_messages.append(text) + + def print_command_result(self, command: str, result: str) -> None: + self.command_results.append((command, result)) + async def prompt_permission(self, event) -> bool: self.permission_events.append(event) return self.permission_allowed @@ -46,6 +54,9 @@ def __init__(self) -> None: self.user_messages: list[str] = [] self.assistant_messages: list[str] = [] + def get_messages(self) -> list: + return [] + def add_user_message(self, message: str) -> None: self.user_messages.append(message) @@ -94,6 +105,8 @@ def make_repl( repl._original_cwd = cwd repl.renderer = FakeRenderer(permission_allowed=permission_allowed) repl._history = RecordingHistory() + repl._command_log = [] + repl._streaming_error_log = [] repl._agent_loop = SimpleNamespace(context_manager=RecordingContextManager()) repl.store = SimpleNamespace(get_state=lambda: SimpleNamespace(permission_context=permission_context)) repl.tool_registry = SimpleNamespace(get=lambda name: tool if name == "bash" else None) @@ -112,6 +125,7 @@ async def test_shell_escape_executes_registered_bash_tool(tmp_path): assert ("STDOUT:\nhello\nExit code: 0", "white") in repl.renderer.messages assert repl.renderer.recorded_turns == [] assert repl._history.appended == [] + assert repl._command_log == [("!echo hello", "$ echo hello\nSTDOUT:\nhello\nExit code: 0", 0, False)] assert repl._agent_loop.context_manager.user_messages == [] assert repl._agent_loop.context_manager.assistant_messages == [] @@ -160,6 +174,7 @@ async def test_shell_escape_error_result_prints_red_output(tmp_path): assert tool.calls == [({"command": "missing-command"}, str(tmp_path))] assert ("STDERR:\nnot found\nExit code: 127", "red") in repl.renderer.messages + assert repl._command_log == [("!missing-command", "$ missing-command\nSTDERR:\nnot found\nExit code: 127", 0, True)] @pytest.mark.asyncio @@ -215,3 +230,25 @@ async def handle_shell_escape(user_input: str) -> None: assert handled == ["!echo hello"] assert history.is_navigating is False assert history.search("") == ["previous prompt"] + + +def test_refresh_banner_replays_shell_escape_command(tmp_path): + from iac_code.state.app_state import AppState + + repl = InlineREPL.__new__(InlineREPL) + repl._session_id = "session-1" + repl._session_name = "deploy-prod" + repl.store = SimpleNamespace(get_state=lambda: AppState(model="test-model", cwd=str(tmp_path))) + repl.console = SimpleNamespace( + file=SimpleNamespace(write=lambda _text: None, flush=lambda: None), + print=lambda *_: None, + ) + repl.renderer = FakeRenderer() + repl._agent_loop = SimpleNamespace(context_manager=SimpleNamespace(get_messages=lambda: [])) + repl._streaming_error_log = [] + repl._command_log = [("!echo hello", "$ echo hello\nhello", 0, False)] + + repl._refresh_banner() + + assert repl.renderer.user_messages == ["!echo hello"] + assert repl.renderer.command_results == [("!echo hello", "$ echo hello\nhello")] From e0000168a3d090ac99402419fae14469a56ed38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 11:01:41 +0800 Subject: [PATCH 06/11] fix: resolve github issue follow-up gaps --- src/iac_code/commands/auth.py | 2 + src/iac_code/services/agent_factory.py | 9 ++-- src/iac_code/services/session_usage.py | 16 ++++-- src/iac_code/tools/base.py | 4 ++ .../tools/cloud/aliyun/ros_stack_instances.py | 5 +- src/iac_code/tools/cloud/registry.py | 10 ++++ src/iac_code/ui/repl.py | 15 ++++-- tests/agent/test_agent_loop_new.py | 6 +-- tests/commands/test_auth_flows.py | 4 +- tests/commands/test_status.py | 8 +-- tests/services/test_agent_factory.py | 51 +++++++++++++++++++ tests/services/test_session_usage.py | 42 +++++++++++++-- .../cloud/aliyun/test_ros_stack_instances.py | 41 +++++++++++++-- tests/tools/cloud/test_registry.py | 19 +++++++ tests/tools/test_base.py | 11 ++++ tests/ui/test_repl_status.py | 4 +- website/docs/cli/command-line-options.md | 2 +- website/docs/cli/commands.md | 6 ++- website/docs/cli/interactive-mode.md | 6 +++ website/docs/cli/sessions.md | 29 ++++++++--- website/docs/cli/skills.md | 9 ++++ .../alibaba-cloud-credentials.md | 18 ++++++- 22 files changed, 276 insertions(+), 41 deletions(-) diff --git a/src/iac_code/commands/auth.py b/src/iac_code/commands/auth.py index b3afff2..ef7deb5 100644 --- a/src/iac_code/commands/auth.py +++ b/src/iac_code/commands/auth.py @@ -807,6 +807,8 @@ async def auth_command(context: "CommandContext | None" = None, **kwargs) -> str if context and hasattr(context, "repl") and context.repl: repl = context.repl repl._reinitialize_provider(repl.store.get_state().model) + if hasattr(repl, "refresh_cloud_tools"): + repl.refresh_cloud_tools() return result diff --git a/src/iac_code/services/agent_factory.py b/src/iac_code/services/agent_factory.py index dbbbbee..dbb85b4 100644 --- a/src/iac_code/services/agent_factory.py +++ b/src/iac_code/services/agent_factory.py @@ -44,8 +44,10 @@ def create_agent_runtime(options: AgentFactoryOptions) -> AgentRuntime: from iac_code.services.cloud_credentials import CloudCredentials from iac_code.services.session_storage import SessionStorage from iac_code.skills.bundled import init_bundled_skills - from iac_code.skills.discovery import discover_all_skills, skill_to_command + from iac_code.skills.discovery import discover_all_skills from iac_code.skills.listing import build_skill_listing + from iac_code.skills.management import build_skill_management_state + from iac_code.skills.settings import load_disabled_skills from iac_code.skills.skill_tool import SkillTool from iac_code.tasks.notification_queue import NotificationQueue from iac_code.tasks.task_state import TaskManager @@ -123,8 +125,8 @@ def create_agent_runtime(options: AgentFactoryOptions) -> AgentRuntime: init_bundled_skills() command_registry = create_default_registry() - for skill in discover_all_skills(cwd): - cmd = skill_to_command(skill) + skill_state = build_skill_management_state(discover_all_skills(cwd), load_disabled_skills()) + for cmd in skill_state.enabled_commands: existing = command_registry.get(cmd.name) if existing is not None and not isinstance(existing, PromptCommand): logger.warning("Skill '{}' skipped: conflicts with built-in command", cmd.name) @@ -139,6 +141,7 @@ def create_agent_runtime(options: AgentFactoryOptions) -> AgentRuntime: provider_manager=provider_manager, tool_registry=tool_registry, system_prompt=base_system_prompt, + disabled_skills=skill_state.disabled_commands, ) ) diff --git a/src/iac_code/services/session_usage.py b/src/iac_code/services/session_usage.py index 13c277f..be98982 100644 --- a/src/iac_code/services/session_usage.py +++ b/src/iac_code/services/session_usage.py @@ -14,6 +14,8 @@ from iac_code.utils.file_security import ensure_private_dir, ensure_private_file from iac_code.utils.project_paths import get_project_dir, get_projects_dir, sanitize_path +USAGE_JSONL_FILENAME = "usage.jsonl" + @dataclass class SessionUsageTotals: @@ -27,7 +29,7 @@ class SessionUsageTotals: @property def total_tokens(self) -> int: - return self.input_tokens + self.output_tokens + self.cache_read_input_tokens + self.cache_creation_input_tokens + return self.input_tokens + self.output_tokens @property def has_recorded_usage(self) -> bool: @@ -61,6 +63,9 @@ def __init__(self, projects_dir: Path | str | None = None) -> None: self._projects_dir = Path(projects_dir) if projects_dir is not None else get_projects_dir() def path_for(self, cwd: str, session_id: str) -> Path: + return self._project_dir_for(cwd) / session_id / USAGE_JSONL_FILENAME + + def legacy_path_for(self, cwd: str, session_id: str) -> Path: return self._project_dir_for(cwd) / f"{session_id}.usage.jsonl" def append( @@ -87,10 +92,14 @@ def append( def load(self, cwd: str, session_id: str) -> SessionUsageTotals: """Load cumulative usage totals, skipping corrupt or unrelated rows.""" - path = self.path_for(cwd, session_id) totals = SessionUsageTotals() + for path in (self.path_for(cwd, session_id), self.legacy_path_for(cwd, session_id)): + self._load_path(path, totals) + return totals + + def _load_path(self, path: Path, totals: SessionUsageTotals) -> None: if not path.exists(): - return totals + return try: with open(path, encoding="utf-8") as f: @@ -108,7 +117,6 @@ def load(self, cwd: str, session_id: str) -> SessionUsageTotals: totals.add(_row_to_usage(row)) except OSError as exc: logger.debug("Failed to load usage sidecar {}: {}", path, exc) - return totals def _project_dir_for(self, cwd: str) -> Path: if self._projects_dir == get_projects_dir(): diff --git a/src/iac_code/tools/base.py b/src/iac_code/tools/base.py index 3f912da..b6e7b17 100644 --- a/src/iac_code/tools/base.py +++ b/src/iac_code/tools/base.py @@ -209,6 +209,10 @@ def register(self, tool: Tool) -> None: """Register a tool.""" self._tools[tool.name] = tool + def unregister(self, name: str) -> None: + """Unregister a tool if it exists.""" + self._tools.pop(name, None) + def get(self, name: str) -> Tool | None: """Get a tool by name.""" return self._tools.get(name) diff --git a/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py b/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py index 9730b3a..0ca95e5 100644 --- a/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py +++ b/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py @@ -195,12 +195,13 @@ async def execute(self, *, tool_input: dict[str, Any], context: ToolContext) -> await asyncio.sleep(self.__class__.poll_interval) try: - status = await self._get_operation_status(client, operation_id, region) + poll_client = self._get_client(region) + status = await self._get_operation_status(poll_client, operation_id, region) except Exception as e: return ToolResult.error(f"[GetStackGroupOperation] {e}") try: - instances = await self._get_instances(client, stack_group_name, region) + instances = await self._get_instances(poll_client, stack_group_name, region) except Exception as e: return ToolResult.error(f"[ListStackInstances] {e}") diff --git a/src/iac_code/tools/cloud/registry.py b/src/iac_code/tools/cloud/registry.py index c062881..0b38a76 100644 --- a/src/iac_code/tools/cloud/registry.py +++ b/src/iac_code/tools/cloud/registry.py @@ -6,8 +6,18 @@ from iac_code.services.cloud_credentials import CloudCredentials from iac_code.tools.base import ToolRegistry +ALIYUN_TOOL_NAMES = ( + "aliyun_api", + "aliyun_doc_search", + "ros_stack", + "ros_stack_instances", +) + def register_cloud_tools(registry: "ToolRegistry", credentials: "CloudCredentials") -> None: + for tool_name in ALIYUN_TOOL_NAMES: + registry.unregister(tool_name) + if credentials.has_provider("aliyun"): from iac_code.tools.cloud.aliyun.aliyun_api import AliyunApi from iac_code.tools.cloud.aliyun.aliyun_doc_search import AliyunDocSearch diff --git a/src/iac_code/ui/repl.py b/src/iac_code/ui/repl.py index 6800040..638cf0a 100644 --- a/src/iac_code/ui/repl.py +++ b/src/iac_code/ui/repl.py @@ -35,7 +35,6 @@ from iac_code.memory.memory_manager import MemoryManager from iac_code.providers.manager import ProviderManager from iac_code.providers.registry import PROVIDER_REGISTRY -from iac_code.services.cloud_credentials import CloudCredentials from iac_code.services.session_index import SessionIndex from iac_code.services.session_metadata import normalize_session_name from iac_code.services.session_resolver import ResolutionStatus, resolve_session_argument @@ -120,10 +119,7 @@ def __init__( self.command_registry = create_default_registry() self.tool_registry = ToolRegistry() self.tool_registry.register_default_tools() - from iac_code.services.cloud_credentials import CloudCredentials - from iac_code.tools.cloud.registry import register_cloud_tools - - register_cloud_tools(self.tool_registry, CloudCredentials()) + self.refresh_cloud_tools() self._current_model = model from iac_code.config import load_active_provider_config @@ -267,6 +263,13 @@ def locked_skill_names(self): """Return skill names that cannot be disabled.""" return getattr(self, "_locked_skill_names", set()) + def refresh_cloud_tools(self) -> None: + """Register cloud tools that are available with current cloud credentials.""" + from iac_code.services.cloud_credentials import CloudCredentials + from iac_code.tools.cloud.registry import register_cloud_tools + + register_cloud_tools(self.tool_registry, CloudCredentials()) + def refresh_skills(self) -> None: """Rediscover skills and refresh enabled/disabled skill state.""" from iac_code.skills.bundled import init_bundled_skills @@ -1334,6 +1337,8 @@ def _status_model(self, fallback: str) -> str: @staticmethod def _status_region() -> str: + from iac_code.services.cloud_credentials import CloudCredentials + credential = CloudCredentials().get_provider("aliyun") return credential.region_id if credential and credential.region_id else "" diff --git a/tests/agent/test_agent_loop_new.py b/tests/agent/test_agent_loop_new.py index 4c6bc8b..d85fa53 100644 --- a/tests/agent/test_agent_loop_new.py +++ b/tests/agent/test_agent_loop_new.py @@ -177,7 +177,7 @@ async def fake_stream(messages, system, tools=None, max_tokens=8192): assert totals.cache_read_input_tokens == 3 assert totals.cache_creation_input_tokens == 2 assert totals.recorded_events == 1 - assert store.load("/tmp/status-project", "usage-session").total_tokens == 20 + assert store.load("/tmp/status-project", "usage-session").total_tokens == 15 async def test_records_usage_with_runtime_provider_key(self, mock_provider, mock_registry, tmp_path, monkeypatch): async def fake_stream(messages, system, tools=None, max_tokens=8192): @@ -711,7 +711,7 @@ async def test_auto_compact_records_response_usage(self, mock_provider, mock_reg assert totals.output_tokens == 4 assert totals.cache_read_input_tokens == 2 assert totals.recorded_events == 1 - assert store.load("/tmp/status-project", "auto-compact-usage").total_tokens == 17 + assert store.load("/tmp/status-project", "auto-compact-usage").total_tokens == 15 async def test_auto_compact_returns_none_without_prompt(self, mock_provider, mock_registry): loop = AgentLoop(provider_manager=mock_provider, system_prompt="test", tool_registry=mock_registry) @@ -764,7 +764,7 @@ async def test_compact_records_response_usage(self, mock_provider, mock_registry assert totals.output_tokens == 6 assert totals.cache_creation_input_tokens == 3 assert totals.recorded_events == 1 - assert store.load("/tmp/status-project", "manual-compact-usage").total_tokens == 22 + assert store.load("/tmp/status-project", "manual-compact-usage").total_tokens == 19 async def test_compact_returns_empty_when_no_messages(self, mock_provider, mock_registry): loop = AgentLoop(provider_manager=mock_provider, system_prompt="test", tool_registry=mock_registry) diff --git a/tests/commands/test_auth_flows.py b/tests/commands/test_auth_flows.py index 05e99cb..dac0701 100644 --- a/tests/commands/test_auth_flows.py +++ b/tests/commands/test_auth_flows.py @@ -42,8 +42,7 @@ async def test_no_context_no_console_in_kwargs(self): @pytest.mark.asyncio async def test_reinitialize_provider_called_after_auth(self, monkeypatch): - """After auth completes, provider should be reinitialized so - credential changes take effect immediately.""" + """After auth completes, provider and cloud tools refresh immediately.""" monkeypatch.setattr( "iac_code.commands.auth._auth_flow", lambda console, store: "Configured: test", @@ -66,6 +65,7 @@ class FakeContext: assert result == "Configured: test" repl._reinitialize_provider.assert_called_once_with("test-model") + repl.refresh_cloud_tools.assert_called_once_with() class TestAuthFlow: diff --git a/tests/commands/test_status.py b/tests/commands/test_status.py index ebc90df..dac9a22 100644 --- a/tests/commands/test_status.py +++ b/tests/commands/test_status.py @@ -66,7 +66,7 @@ async def test_status_prints_recorded_usage_panel() -> None: output_tokens=3280, cache_read_input_tokens=8200, cache_creation_input_tokens=10, - total_tokens=21940, + total_tokens=15730, recorded_events=3, has_recorded_usage=True, ), @@ -93,7 +93,7 @@ async def test_status_prints_recorded_usage_panel() -> None: assert "12,450" in rendered assert "3,280" in rendered assert "8,200" in rendered - assert "21,940" in rendered + assert "15,730" in rendered assert "Cache create" not in rendered assert "7 / 100" in rendered assert "45%" in rendered @@ -184,7 +184,7 @@ async def test_status_aligns_translated_labels_by_display_width(monkeypatch) -> input_tokens=43210, output_tokens=5678, cache_read_input_tokens=9012, - total_tokens=52800, + total_tokens=48888, has_recorded_usage=True, ), "turn_count": 7, @@ -216,7 +216,7 @@ async def test_status_aligns_translated_labels_by_display_width(monkeypatch) -> "43,210", "5,678", "9,012", - "52,800", + "48,888", ] } diff --git a/tests/services/test_agent_factory.py b/tests/services/test_agent_factory.py index 91755ab..c97e031 100644 --- a/tests/services/test_agent_factory.py +++ b/tests/services/test_agent_factory.py @@ -79,3 +79,54 @@ def test_create_agent_runtime_auto_session_id(tmp_path, monkeypatch) -> None: assert runtime.session_id is not None assert len(runtime.session_id) == 8 # uuid4()[:8] + + +def test_create_agent_runtime_respects_disabled_skills(tmp_path, monkeypatch) -> None: + from iac_code.skills.frontmatter import SkillFrontmatter + from iac_code.skills.skill_definition import SkillDefinition + from iac_code.types.skill_source import SkillSource + + monkeypatch.chdir(tmp_path) + monkeypatch.setenv("IAC_CODE_CONFIG_DIR", str(tmp_path / "config")) + + enabled_skill = SkillDefinition( + name="enabled-skill", + description="Enabled skill", + frontmatter=SkillFrontmatter(description="Enabled skill", auto_trigger={"script": "auto_trigger.py"}), + content="Enabled body", + source=SkillSource.PROJECT, + ) + disabled_skill = SkillDefinition( + name="disabled-skill", + description="Disabled skill", + frontmatter=SkillFrontmatter(description="Disabled skill", auto_trigger={"script": "auto_trigger.py"}), + content="Disabled body", + source=SkillSource.PROJECT, + ) + + monkeypatch.setattr( + "iac_code.skills.discovery.discover_all_skills", + lambda cwd: [enabled_skill, disabled_skill], + ) + monkeypatch.setattr("iac_code.skills.settings.load_disabled_skills", lambda: {"disabled-skill"}) + + captured_listing = {} + + def fake_build_skill_listing(commands): + captured_listing["names"] = [command.name for command in commands] + return "skill listing" + + monkeypatch.setattr("iac_code.skills.listing.build_skill_listing", fake_build_skill_listing) + + runtime = create_agent_runtime( + AgentFactoryOptions(model="qwen3.6-plus", session_id="skill-runtime", cwd=str(tmp_path)) + ) + + assert runtime.command_registry.get("enabled-skill") is not None + assert runtime.command_registry.get("disabled-skill") is None + assert captured_listing["names"] == ["enabled-skill"] + assert [command.name for command in runtime.agent_loop._auto_trigger_skills] == ["enabled-skill"] + + skill_tool = runtime.tool_registry.get("skill") + assert skill_tool is not None + assert "disabled-skill" in skill_tool._disabled_skills diff --git a/tests/services/test_session_usage.py b/tests/services/test_session_usage.py index 6ff83f2..a507f30 100644 --- a/tests/services/test_session_usage.py +++ b/tests/services/test_session_usage.py @@ -16,7 +16,7 @@ def test_totals_adds_usage_and_tracks_record_count() -> None: assert totals.output_tokens == 6 assert totals.cache_read_input_tokens == 3 assert totals.cache_creation_input_tokens == 2 - assert totals.total_tokens == 28 + assert totals.total_tokens == 23 assert totals.recorded_events == 2 assert totals.has_recorded_usage is True @@ -48,7 +48,7 @@ def test_append_and_load_round_trip(tmp_path) -> None: assert totals.output_tokens == 5 assert totals.cache_read_input_tokens == 4 assert totals.cache_creation_input_tokens == 1 - assert totals.total_tokens == 27 + assert totals.total_tokens == 22 assert totals.recorded_events == 2 lines = store.path_for(CWD, "s2").read_text(encoding="utf-8").splitlines() @@ -84,5 +84,41 @@ def test_load_skips_corrupt_and_unrelated_rows(tmp_path) -> None: assert totals.output_tokens == 8 assert totals.cache_read_input_tokens == 1 assert totals.cache_creation_input_tokens == 0 - assert totals.total_tokens == 16 + assert totals.total_tokens == 15 + assert totals.recorded_events == 2 + + +def test_path_for_uses_directory_session_layout(tmp_path) -> None: + store = SessionUsageStore(projects_dir=tmp_path) + + path = store.path_for(CWD, "s4") + + assert path == tmp_path / "-tmp-status-project" / "s4" / "usage.jsonl" + + +def test_load_reads_new_and_legacy_sidecars(tmp_path) -> None: + store = SessionUsageStore(projects_dir=tmp_path) + new_path = store.path_for(CWD, "s5") + legacy_path = store.legacy_path_for(CWD, "s5") + new_path.parent.mkdir(parents=True) + legacy_path.parent.mkdir(parents=True, exist_ok=True) + + new_path.write_text( + '{"type":"usage","version":1,"input_tokens":4,"output_tokens":6,' + '"cache_read_input_tokens":1,"cache_creation_input_tokens":0}\n', + encoding="utf-8", + ) + legacy_path.write_text( + '{"type":"usage","version":1,"input_tokens":3,"output_tokens":2,' + '"cache_read_input_tokens":5,"cache_creation_input_tokens":7}\n', + encoding="utf-8", + ) + + totals = store.load(CWD, "s5") + + assert totals.input_tokens == 7 + assert totals.output_tokens == 8 + assert totals.cache_read_input_tokens == 6 + assert totals.cache_creation_input_tokens == 7 + assert totals.total_tokens == 15 assert totals.recorded_events == 2 diff --git a/tests/tools/cloud/aliyun/test_ros_stack_instances.py b/tests/tools/cloud/aliyun/test_ros_stack_instances.py index 58d8420..f7ca1ac 100644 --- a/tests/tools/cloud/aliyun/test_ros_stack_instances.py +++ b/tests/tools/cloud/aliyun/test_ros_stack_instances.py @@ -26,10 +26,9 @@ def mock_credentials(): @pytest.fixture -def tool() -> RosStackInstances: - t = RosStackInstances() - t.poll_interval = 0 - return t +def tool(monkeypatch) -> RosStackInstances: + monkeypatch.setattr(RosStackInstances, "poll_interval", 0) + return RosStackInstances() @pytest.fixture @@ -198,6 +197,40 @@ async def test_execute_create_instances(self, tool: RosStackInstances, mock_cred assert first.instances[0]["region_id"] == "cn-hangzhou" assert first.instances[0]["status"] == "SUCCEEDED" + @pytest.mark.asyncio + async def test_execute_reacquires_clients_while_polling(self, tool: RosStackInstances) -> None: + initiate_client = MagicMock(name="initiate-client") + first_poll_client = MagicMock(name="first-poll-client") + second_poll_client = MagicMock(name="second-poll-client") + + clients = [initiate_client, first_poll_client, second_poll_client] + statuses = ["RUNNING", "SUCCEEDED"] + status_clients = [] + instance_clients = [] + + async def fake_get_operation_status(client, operation_id, region): + status_clients.append(client) + return statuses.pop(0) + + async def fake_get_instances(client, stack_group_name, region): + instance_clients.append(client) + return [] + + with ( + patch.object(tool, "_get_client", side_effect=clients), + patch.object(tool, "_initiate", return_value="op-1"), + patch.object(tool, "_get_operation_status", side_effect=fake_get_operation_status), + patch.object(tool, "_get_instances", side_effect=fake_get_instances), + ): + result = await tool.execute( + tool_input={"action": "CreateStackInstances", "params": {"StackGroupName": "demo"}}, + context=ToolContext(), + ) + + assert result.is_error is False + assert status_clients == [first_poll_client, second_poll_client] + assert instance_clients == [first_poll_client, second_poll_client] + @pytest.mark.asyncio async def test_execute_returns_initiate_error(self, tool: RosStackInstances) -> None: with ( diff --git a/tests/tools/cloud/test_registry.py b/tests/tools/cloud/test_registry.py index 7385205..8fbe826 100644 --- a/tests/tools/cloud/test_registry.py +++ b/tests/tools/cloud/test_registry.py @@ -20,5 +20,24 @@ def test_does_not_register_when_not_configured(self): credentials.has_provider.return_value = False register_cloud_tools(registry, credentials) assert registry.get("aliyun_api") is None + assert registry.get("aliyun_doc_search") is None + assert registry.get("ros_stack") is None + assert registry.get("ros_stack_instances") is None + + def test_removes_stale_aliyun_tools_when_credentials_become_unavailable(self): + registry = ToolRegistry() + credentials = MagicMock() + credentials.has_provider.side_effect = [True, False] + + register_cloud_tools(registry, credentials) + assert registry.get("aliyun_api") is not None + assert registry.get("aliyun_doc_search") is not None + assert registry.get("ros_stack") is not None + assert registry.get("ros_stack_instances") is not None + + register_cloud_tools(registry, credentials) + + assert registry.get("aliyun_api") is None + assert registry.get("aliyun_doc_search") is None assert registry.get("ros_stack") is None assert registry.get("ros_stack_instances") is None diff --git a/tests/tools/test_base.py b/tests/tools/test_base.py index df663e4..18caa76 100644 --- a/tests/tools/test_base.py +++ b/tests/tools/test_base.py @@ -105,6 +105,17 @@ def test_get_nonexistent_tool(self): registry = ToolRegistry() assert registry.get("nonexistent") is None + def test_unregister_removes_tool_if_registered(self): + """Test unregistering a tool removes it from the registry.""" + registry = ToolRegistry() + tool = DummyTool() + registry.register(tool) + + registry.unregister("dummy") + registry.unregister("missing") + + assert registry.get("dummy") is None + def test_list_tools(self): """Test listing all registered tools.""" registry = ToolRegistry() diff --git a/tests/ui/test_repl_status.py b/tests/ui/test_repl_status.py index c9c48f3..5b3db90 100644 --- a/tests/ui/test_repl_status.py +++ b/tests/ui/test_repl_status.py @@ -49,7 +49,7 @@ def test_status_snapshot_uses_agent_loop_and_original_cwd(monkeypatch) -> None: monkeypatch.setattr("iac_code.ui.repl.get_active_provider_key", lambda: "dashscope") monkeypatch.setattr( - "iac_code.ui.repl.CloudCredentials", + "iac_code.services.cloud_credentials.CloudCredentials", lambda: SimpleNamespace(get_provider=lambda name: SimpleNamespace(region_id="cn-beijing")), ) @@ -88,7 +88,7 @@ def test_status_snapshot_uses_runtime_provider_manager(monkeypatch) -> None: monkeypatch.setattr("iac_code.ui.repl.get_active_provider_key", lambda: "openai") monkeypatch.setattr( - "iac_code.ui.repl.CloudCredentials", + "iac_code.services.cloud_credentials.CloudCredentials", lambda: SimpleNamespace(get_provider=lambda name: None), ) diff --git a/website/docs/cli/command-line-options.md b/website/docs/cli/command-line-options.md index 7252abf..f5bf626 100644 --- a/website/docs/cli/command-line-options.md +++ b/website/docs/cli/command-line-options.md @@ -16,7 +16,7 @@ Command line options change how IaC Code starts. Use them before entering the in | `--output-format ` | Set output format for non-interactive mode. Supported values are `text`, `json`, and `stream-json`. The default is `text`. | | `--max-turns ` | Limit the maximum number of agent turns in non-interactive mode. The default is `100`. | | `-d`, `--debug` | Enable debug logging for the current run. In interactive mode, use `/debug` to inspect or change debug logging after startup. | -| `-r `, `--resume ` | Resume a previous session by ID. This is for returning to a known conversation. | +| `-r `, `--resume ` | Resume a previous session by exact session ID, unique ID prefix, or unique session name. Cross-project resolved sessions print a `cd ... && iac-code --resume ` command instead of hot-swapping the current project. | | `-c`, `--continue` | Resume the most recent session. This cannot be used together with `--resume`. | | `--allowed-tools ` | Comma-separated tool permission patterns to allow, e.g. `'bash(git *),write_file'`. | | `--disallowed-tools ` | Comma-separated tool permission patterns to deny, e.g. `'bash(rm *)'`. | diff --git a/website/docs/cli/commands.md b/website/docs/cli/commands.md index a77dd7b..8257c29 100644 --- a/website/docs/cli/commands.md +++ b/website/docs/cli/commands.md @@ -20,7 +20,11 @@ Text after the command name is passed as arguments. In the table below, `` | `/effort [level]` | Show or change thinking effort for the active model when the selected model supports effort control. With a level, it applies the requested value if valid for the model. Without a level, it opens an interactive picker in the REPL, or prints the current effort in non-interactive contexts. | | `/exit` | Exit the interactive REPL. Aliases: `/quit`, `/q`. | | `/help` | Show available commands and common keyboard shortcuts inside the REPL. Alias: `/?`. | +| `/memory [\|search \|delete \|help]` | List, view, search, or delete saved memories. Natural-language memory creation is still handled by the assistant through the memory tool when you ask it to remember something. | | `/model [model_name]` | Show or switch the active model. With `model_name`, it switches directly to that model for the active provider. Without an argument, it opens an interactive model picker when a provider is configured, or prints the current model when no console UI is available. | -| `/resume [conversation id or search term]` | Resume a previous session. With an argument, IaC Code resolves it as a session ID or unique ID prefix. Without an argument, it opens the interactive session picker. Cross-project sessions print a `cd ... && iac-code --resume ` command instead of hot-swapping the current project. | +| `/rename ` | Name the current session. Names appear in the welcome banner, exit hint, and `/resume` picker, and can be used with `/resume` or `--resume` when they uniquely identify a session. | +| `/resume [session id\|unique id prefix\|unique session name]` | Resume a previous session. With an argument, IaC Code resolves it as an exact session ID, unique ID prefix, or unique session name. Without an argument, it opens the interactive session picker. Cross-project sessions print a `cd ... && iac-code --resume ` command instead of hot-swapping the current project. | +| `/skills` | Open the skill management picker. Search skills, sort by name/source/size, and enable or disable user and project skills. Bundled skills remain locked on. | +| `/status` | Show current session ID, provider, model, Alibaba Cloud region, working directory, recorded API token usage, turn count, and context utilization. | The exact command list can change between releases. Use `/help` or type `/` in the REPL to inspect the commands available in your installed version. diff --git a/website/docs/cli/interactive-mode.md b/website/docs/cli/interactive-mode.md index c1e536b..2e33bd3 100644 --- a/website/docs/cli/interactive-mode.md +++ b/website/docs/cli/interactive-mode.md @@ -25,6 +25,12 @@ Then describe what you want to build: Create a VPC, two ECS instances, and a security group that allows SSH from my office IP. ``` +## Commands + +Type `/` to discover available slash commands. Common operational commands include `/status` for the current session state, `/skills` for skill management, `/memory` for saved memories, `/rename` for naming the active session, and `/resume` for switching sessions. + +Type `$` to discover and invoke skills only. + ## Editing input Use `Shift+Enter` to insert a newline without sending the prompt. Press `Enter` diff --git a/website/docs/cli/sessions.md b/website/docs/cli/sessions.md index 2952b56..e10b12a 100644 --- a/website/docs/cli/sessions.md +++ b/website/docs/cli/sessions.md @@ -17,20 +17,37 @@ In the REPL, use the `/resume` command: /resume ``` -This opens an interactive picker showing recent sessions for the current project, with their last prompt as the title. +This opens an interactive picker showing recent sessions for the current project, with the session name as the title when set, otherwise the last prompt or first prompt fallback. -To resume a specific session by ID or ID prefix: +To resume a specific session by exact session ID, unique ID prefix, or unique session name: ```text /resume abc123 ``` +### Naming Sessions + +Use `/rename` to give the active session a stable, human-readable name: + +```text +/rename deploy-prod +``` + +The name is stored in the session metadata. It appears in the welcome banner when you resume, in the exit hint, and in the `/resume` picker. + +You can resume by name when it uniquely identifies a session: + +```text +/resume deploy-prod +iac-code --resume deploy-prod +``` + ### CLI: `--resume` and `--continue` -Resume a specific session from the command line: +Resume a specific session from the command line by exact session ID, unique ID prefix, or unique session name: ```bash -iac-code --resume +iac-code --resume ``` Resume the most recent session: @@ -42,7 +59,7 @@ iac-code --continue The short flags `-r` and `-c` are also available: ```bash -iac-code -r +iac-code -r iac-code -c ``` @@ -66,7 +83,7 @@ The `/resume` picker displays: | Column | Description | |--------|-------------| -| Title | Last user prompt (or first prompt if no metadata) | +| Title | Session name when set, otherwise last user prompt or first prompt | | Branch | Git branch at the time of the session | | Time | Last modification time | diff --git a/website/docs/cli/skills.md b/website/docs/cli/skills.md index bafcfd8..87a93cd 100644 --- a/website/docs/cli/skills.md +++ b/website/docs/cli/skills.md @@ -89,6 +89,14 @@ paths: | `agent` | No | `"general-purpose"` | Agent type for fork mode | | `paths` | No | `[]` | Glob patterns for path-based auto-activation | +## Managing Skills + +Run `/skills` in the interactive REPL to open the skill management picker. The picker lists discovered bundled, user, and project skills with their source, size, and enabled state. You can search by name or description, sort by name/source/size, and toggle user or project skills on and off. + +Disabled skills are saved in `settings.yml` under `disabled_skills`. Bundled skills are locked enabled and are not written to the disabled list. + +Use `$` when you want autocomplete and invocation to target skills only. This is useful when a skill name overlaps with ordinary text or when you want to avoid built-in slash commands. + ## Execution Modes ### Inline (default) @@ -188,3 +196,4 @@ Save this as `~/.iac-code/skills/checklist.md` or `.iac-code/skills/checklist.md - **Bundled skills** are always allowed automatically. - **User/project skills** with no shell commands and no `allowed_tools` are auto-allowed. - **Other skills** prompt for user confirmation on first use. +- **Disabled user/project skills** are hidden from model-visible skill listings and automatic triggers, and direct `skill` tool calls return a disabled-skill error. diff --git a/website/docs/configuration/alibaba-cloud-credentials.md b/website/docs/configuration/alibaba-cloud-credentials.md index d620dfa..abed55b 100644 --- a/website/docs/configuration/alibaba-cloud-credentials.md +++ b/website/docs/configuration/alibaba-cloud-credentials.md @@ -7,7 +7,23 @@ description: Configure Alibaba Cloud AccessKey or STS credentials. Alibaba Cloud credentials are required for operations that inspect or manage cloud resources. -Supported environment variables: +## OAuth Browser Login + +The recommended interactive setup path is `/auth`: + +```text +/auth +``` + +Choose **Configure IaC Cloud Service**, then **Alibaba Cloud**, then **OAuth Login (Browser)**. IaC Code opens a browser authorization flow, listens for the local callback, exchanges the authorization code with PKCE, and saves OAuth-backed temporary credentials to `.cloud-credentials.yml` under the IaC Code config directory. + +During setup you can choose the China or International OAuth site. IaC Code stores the selected site with the refresh token so future refreshes use the same endpoint. + +OAuth credentials are refreshed automatically when the access token or STS credentials are near expiration. If the refresh token expires or is revoked, run `/auth` again and choose OAuth Login (Browser). + +## Environment Variables + +Environment variables are still supported for AccessKey and STS workflows: | Variable | Description | |---|---| From 97daa6e7af9d7f912a3fdf3f169ba51fe090d5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 11:41:01 +0800 Subject: [PATCH 07/11] feat: support qwen3.7-plus --- .../i18n/locales/de/LC_MESSAGES/messages.po | 260 +++++++++--------- .../i18n/locales/es/LC_MESSAGES/messages.po | 260 +++++++++--------- .../i18n/locales/fr/LC_MESSAGES/messages.po | 260 +++++++++--------- .../i18n/locales/ja/LC_MESSAGES/messages.po | 260 +++++++++--------- .../i18n/locales/pt/LC_MESSAGES/messages.po | 260 +++++++++--------- .../i18n/locales/zh/LC_MESSAGES/messages.po | 260 +++++++++--------- src/iac_code/providers/registry.py | 1 + src/iac_code/providers/thinking.py | 1 + 8 files changed, 782 insertions(+), 780 deletions(-) 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 a1e47a8..b5f5b50 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 22:08+0800\n" +"POT-Creation-Date: 2026-06-03 11:39+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: de\n" @@ -127,7 +127,7 @@ msgstr "Verwendung: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "Der Speicher-Manager ist nicht verfügbar." -#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:21 msgid "Usage: /rename " msgstr "Verwendung: /rename " @@ -135,18 +135,18 @@ msgstr "Verwendung: /rename " msgid "Rename is only available after a session is created." msgstr "Umbenennen ist erst verfügbar, nachdem eine Sitzung erstellt wurde." -#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:42 #, python-brace-format msgid "Session is already named {name}" msgstr "Die Sitzung heißt bereits {name}" -#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:43 #, python-brace-format msgid "Renamed session to {name}" msgstr "Sitzung in {name} umbenannt" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 +#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 msgid "Permission denied." msgstr "Zugriff verweigert." @@ -711,7 +711,7 @@ msgstr "Skills verwalten" msgid "Show current session status" msgstr "Aktuellen Sitzungsstatus anzeigen" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1117 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navigieren" @@ -719,8 +719,8 @@ msgstr "Navigieren" #: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 #: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 #: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1117 +#: src/iac_code/commands/auth.py:1551 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Bestätigen" @@ -728,8 +728,8 @@ msgstr "Bestätigen" #: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 #: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 #: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 -#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 -#: src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:1117 src/iac_code/commands/auth.py:1443 +#: src/iac_code/commands/auth.py:1551 msgid "Back" msgstr "Zurück" @@ -741,9 +741,9 @@ msgstr "Behalten" msgid "Re-enter" msgstr "Erneut eingeben" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 -#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 -#: src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:863 +#: src/iac_code/commands/auth.py:921 src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:956 msgid " (current)" msgstr " (aktuell)" @@ -768,247 +768,247 @@ msgstr "Benutzerdefinierten Modellnamen eingeben: " msgid "Error: console not available" msgstr "Fehler: Konsole nicht verfügbar" -#: src/iac_code/commands/auth.py:818 +#: src/iac_code/commands/auth.py:820 msgid "Configure LLM Provider" msgstr "LLM-Anbieter konfigurieren" -#: src/iac_code/commands/auth.py:819 +#: src/iac_code/commands/auth.py:821 msgid "Configure IaC Cloud Service" msgstr "IaC-Cloud-Dienst konfigurieren" -#: src/iac_code/commands/auth.py:821 +#: src/iac_code/commands/auth.py:823 msgid "Select configuration type" msgstr "Konfigurationstyp auswählen" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 -#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 -#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:825 src/iac_code/commands/auth.py:981 +#: src/iac_code/commands/auth.py:997 src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1490 src/iac_code/commands/auth.py:1531 msgid "Auth cancelled" msgstr "Authentifizierung abgebrochen" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 -#: src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:961 +#: src/iac_code/commands/auth.py:1051 #, python-brace-format msgid "Select provider — {group}" msgstr "Anbieter auswählen — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:1036 msgid "Third-party" msgstr "Drittanbieter" -#: src/iac_code/commands/auth.py:875 +#: src/iac_code/commands/auth.py:877 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 +#: src/iac_code/commands/auth.py:878 src/iac_code/commands/auth.py:1029 msgid "Configured" msgstr "Konfiguriert" -#: src/iac_code/commands/auth.py:931 +#: src/iac_code/commands/auth.py:933 msgid "Select provider" msgstr "Anbieter auswählen" -#: src/iac_code/commands/auth.py:972 +#: src/iac_code/commands/auth.py:974 #, python-brace-format msgid "Configure {provider}" msgstr "{provider} konfigurieren" -#: src/iac_code/commands/auth.py:988 +#: src/iac_code/commands/auth.py:990 #, python-brace-format msgid "Enter API key for {provider}" msgstr "API-Key für {provider} eingeben" -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:1028 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 +#: src/iac_code/commands/auth.py:1037 src/iac_code/commands/auth.py:1058 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:427 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1037 +#: src/iac_code/commands/auth.py:1039 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1038 +#: src/iac_code/commands/auth.py:1040 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:429 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1042 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:420 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:418 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:419 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:422 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1047 src/iac_code/providers/registry.py:435 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1048 src/iac_code/providers/registry.py:434 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1047 +#: src/iac_code/commands/auth.py:1049 msgid "Local" msgstr "Lokal" -#: src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1050 msgid "Compatible" msgstr "Kompatibel" -#: src/iac_code/commands/auth.py:1065 +#: src/iac_code/commands/auth.py:1067 msgid "Select Cloud Provider" msgstr "Cloud-Anbieter auswählen" -#: src/iac_code/commands/auth.py:1081 +#: src/iac_code/commands/auth.py:1083 msgid "Credential" msgstr "Anmeldedaten" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 -#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/auth.py:1084 src/iac_code/commands/auth.py:1199 +#: src/iac_code/commands/auth.py:1527 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Region" -#: src/iac_code/commands/auth.py:1084 +#: src/iac_code/commands/auth.py:1086 msgid "Configure Alibaba Cloud" msgstr "Alibaba Cloud konfigurieren" -#: src/iac_code/commands/auth.py:1186 +#: src/iac_code/commands/auth.py:1188 msgid "Current configuration" msgstr "Aktuelle Konfiguration" -#: src/iac_code/commands/auth.py:1188 +#: src/iac_code/commands/auth.py:1190 msgid "Mode" msgstr "Modus" -#: src/iac_code/commands/auth.py:1194 +#: src/iac_code/commands/auth.py:1196 msgid "(not set)" msgstr "(nicht gesetzt)" -#: src/iac_code/commands/auth.py:1203 +#: src/iac_code/commands/auth.py:1205 msgid "AccessKey" msgstr "AccessKey" -#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1207 src/iac_code/commands/auth.py:1219 msgid "STS Token" msgstr "STS-Token" -#: src/iac_code/commands/auth.py:1207 +#: src/iac_code/commands/auth.py:1209 msgid "RAM Role" msgstr "RAM-Rolle" -#: src/iac_code/commands/auth.py:1209 +#: src/iac_code/commands/auth.py:1211 msgid "OAuth Login (Browser)" msgstr "OAuth-Anmeldung (Browser)" -#: src/iac_code/commands/auth.py:1215 +#: src/iac_code/commands/auth.py:1217 msgid "AccessKey ID" msgstr "AccessKey-ID" -#: src/iac_code/commands/auth.py:1216 +#: src/iac_code/commands/auth.py:1218 msgid "AccessKey Secret" msgstr "AccessKey-Secret" -#: src/iac_code/commands/auth.py:1218 +#: src/iac_code/commands/auth.py:1220 msgid "RAM Role ARN" msgstr "RAM-Rollen-ARN" -#: src/iac_code/commands/auth.py:1219 +#: src/iac_code/commands/auth.py:1221 msgid "Session Name" msgstr "Sitzungsname" -#: src/iac_code/commands/auth.py:1220 +#: src/iac_code/commands/auth.py:1222 msgid "OAuth Site Type" msgstr "OAuth-Standorttyp" -#: src/iac_code/commands/auth.py:1221 +#: src/iac_code/commands/auth.py:1223 msgid "OAuth Access Token" msgstr "OAuth-Zugriffstoken" -#: src/iac_code/commands/auth.py:1222 +#: src/iac_code/commands/auth.py:1224 msgid "OAuth Refresh Token" msgstr "OAuth-Refresh-Token" -#: src/iac_code/commands/auth.py:1223 +#: src/iac_code/commands/auth.py:1225 msgid "OAuth Access Token Expire" msgstr "Ablaufzeit des OAuth-Zugriffstokens" -#: src/iac_code/commands/auth.py:1224 +#: src/iac_code/commands/auth.py:1226 msgid "OAuth Refresh Token Expire" msgstr "Ablaufzeit des OAuth-Refresh-Tokens" -#: src/iac_code/commands/auth.py:1225 +#: src/iac_code/commands/auth.py:1227 msgid "STS Expiration" msgstr "STS-Ablaufzeit" -#: src/iac_code/commands/auth.py:1382 +#: src/iac_code/commands/auth.py:1384 msgid "China" msgstr "China" -#: src/iac_code/commands/auth.py:1383 +#: src/iac_code/commands/auth.py:1385 msgid "International" msgstr "International" -#: src/iac_code/commands/auth.py:1385 +#: src/iac_code/commands/auth.py:1387 msgid "Choose site type" msgstr "Standorttyp auswählen" -#: src/iac_code/commands/auth.py:1400 +#: src/iac_code/commands/auth.py:1402 #, python-brace-format msgid "Alibaba Cloud OAuth login failed: {error}" msgstr "Alibaba Cloud OAuth-Anmeldung fehlgeschlagen: {error}" -#: src/iac_code/commands/auth.py:1416 +#: src/iac_code/commands/auth.py:1418 msgid "Configured: Alibaba Cloud OAuth credentials saved" msgstr "Konfiguriert: Alibaba Cloud-OAuth-Anmeldedaten gespeichert" -#: src/iac_code/commands/auth.py:1428 +#: src/iac_code/commands/auth.py:1430 msgid "Configure Alibaba Cloud credentials" msgstr "Alibaba Cloud-Anmeldedaten konfigurieren" -#: src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1443 msgid "Reconfigure credential" msgstr "Anmeldedaten neu konfigurieren" -#: src/iac_code/commands/auth.py:1454 +#: src/iac_code/commands/auth.py:1456 msgid "Select credential type" msgstr "Anmeldedatentyp auswählen" -#: src/iac_code/commands/auth.py:1510 +#: src/iac_code/commands/auth.py:1512 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "Konfiguriert: Alibaba Cloud-Anmeldedaten unter ~/.iac-code gespeichert" -#: src/iac_code/commands/auth.py:1517 +#: src/iac_code/commands/auth.py:1519 msgid "Configure Alibaba Cloud region" msgstr "Alibaba Cloud-Region konfigurieren" -#: src/iac_code/commands/auth.py:1543 +#: src/iac_code/commands/auth.py:1545 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Konfiguriert: Alibaba Cloud-Region unter ~/.iac-code gespeichert" @@ -1158,11 +1158,11 @@ msgstr "Aktuelles Modell: {model}" msgid "Kept model as {model}" msgstr "Modell beibehalten: {model}" -#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +#: src/iac_code/commands/rename.py:16 src/iac_code/commands/rename.py:28 msgid "Rename is only available in interactive mode." msgstr "Umbenennen ist nur im interaktiven Modus verfügbar." -#: src/iac_code/commands/rename.py:30 +#: src/iac_code/commands/rename.py:31 msgid "Rename cancelled" msgstr "Umbenennen abgebrochen" @@ -1366,79 +1366,79 @@ msgstr "" " Base URL korrekt ist (aktuell: {base_url}). Viele OpenAI-kompatible " "Endpunkte erfordern ein /v1-Suffix (z. B. {base_url}/v1)." -#: src/iac_code/providers/registry.py:415 +#: src/iac_code/providers/registry.py:416 msgid "Alibaba Cloud Bailian" msgstr "Alibaba Cloud Bailian" -#: src/iac_code/providers/registry.py:416 +#: src/iac_code/providers/registry.py:417 msgid "Alibaba Cloud Bailian Token Plan" msgstr "Alibaba Cloud Bailian Token Plan" -#: src/iac_code/providers/registry.py:420 +#: src/iac_code/providers/registry.py:421 msgid "OpenAPI Compatible" msgstr "OpenAPI-kompatibel" -#: src/iac_code/providers/registry.py:422 +#: src/iac_code/providers/registry.py:423 msgid "Kimi (China)" msgstr "Kimi (China)" -#: src/iac_code/providers/registry.py:423 +#: src/iac_code/providers/registry.py:424 msgid "Kimi (International)" msgstr "Kimi (International)" -#: src/iac_code/providers/registry.py:424 +#: src/iac_code/providers/registry.py:425 msgid "MiniMax (China)" msgstr "MiniMax (China)" -#: src/iac_code/providers/registry.py:425 +#: src/iac_code/providers/registry.py:426 msgid "MiniMax (International)" msgstr "MiniMax (International)" -#: src/iac_code/providers/registry.py:427 +#: src/iac_code/providers/registry.py:428 msgid "ZhiPu AI (International)" msgstr "ZhiPu AI (International)" -#: src/iac_code/providers/registry.py:429 +#: src/iac_code/providers/registry.py:430 msgid "SiliconFlow (China)" msgstr "SiliconFlow (China)" -#: src/iac_code/providers/registry.py:430 +#: src/iac_code/providers/registry.py:431 msgid "SiliconFlow (International)" msgstr "SiliconFlow (International)" -#: src/iac_code/providers/registry.py:431 +#: src/iac_code/providers/registry.py:432 msgid "Ollama (Local)" msgstr "Ollama (Lokal)" -#: src/iac_code/providers/registry.py:432 +#: src/iac_code/providers/registry.py:433 msgid "LM Studio (Local)" msgstr "LM Studio (Lokal)" -#: src/iac_code/providers/registry.py:435 +#: src/iac_code/providers/registry.py:436 msgid "ModelScope" msgstr "ModelScope" -#: src/iac_code/providers/registry.py:436 +#: src/iac_code/providers/registry.py:437 msgid "Alibaba Cloud CodingPlan" msgstr "Alibaba Cloud CodingPlan" -#: src/iac_code/providers/registry.py:437 +#: src/iac_code/providers/registry.py:438 msgid "Alibaba Cloud CodingPlan (International)" msgstr "Alibaba Cloud CodingPlan (International)" -#: src/iac_code/providers/registry.py:438 +#: src/iac_code/providers/registry.py:439 msgid "ZhiPu AI CodingPlan" msgstr "ZhiPu AI CodingPlan" -#: src/iac_code/providers/registry.py:439 +#: src/iac_code/providers/registry.py:440 msgid "ZhiPu AI CodingPlan (International)" msgstr "ZhiPu AI CodingPlan (International)" -#: src/iac_code/providers/registry.py:440 +#: src/iac_code/providers/registry.py:441 msgid "Volcengine CodingPlan" msgstr "Volcengine CodingPlan" -#: src/iac_code/providers/registry.py:441 +#: src/iac_code/providers/registry.py:442 msgid "Anthropic Compatible" msgstr "Anthropic-kompatibel" @@ -1597,7 +1597,7 @@ msgstr "" "wieder an, und führen Sie /auth aus, um erneut OAuth-Anmeldung (Browser) " "zu wählen." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "" @@ -2325,115 +2325,115 @@ msgstr "Nein, immer \"{rule}\" ablehnen (diese Sitzung)" msgid "No, always reject this tool" msgstr "Nein, dieses Tool immer ablehnen" -#: src/iac_code/ui/repl.py:412 +#: src/iac_code/ui/repl.py:423 msgid "Press Ctrl+C again to exit." msgstr "Drücken Sie erneut Ctrl+C zum Beenden." -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:448 msgid "Interrupted." msgstr "Unterbrochen." -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:507 msgid "Update now" msgstr "Jetzt aktualisieren" -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:509 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Führt den angezeigten Aktualisierungsbefehl aus und beendet das Programm " "bei Erfolg." -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:512 msgid "Skip" msgstr "Überspringen" -#: src/iac_code/ui/repl.py:503 +#: src/iac_code/ui/repl.py:514 msgid "Continue with the current version for this session." msgstr "Für diese Sitzung mit der aktuellen Version fortfahren." -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:517 msgid "Skip until next version" msgstr "Bis zur nächsten Version überspringen" -#: src/iac_code/ui/repl.py:508 +#: src/iac_code/ui/repl.py:519 msgid "Hide this update until a newer version is available." msgstr "Dieses Update ausblenden, bis eine neuere Version verfügbar ist." -#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 +#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 msgid "Update command failed. Continuing with the current version." msgstr "" "Der Aktualisierungsbefehl ist fehlgeschlagen. Es wird mit der aktuellen " "Version fortgefahren." -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:543 msgid "Update completed. Restart iac-code to continue." msgstr "Update abgeschlossen. Starten Sie iac-code neu, um fortzufahren." -#: src/iac_code/ui/repl.py:570 +#: src/iac_code/ui/repl.py:581 msgid "No image in clipboard." msgstr "Kein Bild in der Zwischenablage." -#: src/iac_code/ui/repl.py:756 +#: src/iac_code/ui/repl.py:767 msgid "Usage: !" msgstr "Verwendung: !" -#: src/iac_code/ui/repl.py:761 +#: src/iac_code/ui/repl.py:774 msgid "Shell command support is unavailable." msgstr "Shell-Befehlsunterstützung ist nicht verfügbar." -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:844 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "Unbekannter Skill: ${name}. Tippe /, um Befehle und Skills aufzulisten." -#: src/iac_code/ui/repl.py:830 +#: src/iac_code/ui/repl.py:846 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available commands.Unbekannter " "Befehl: /{name}. Geben Sie /help für verfügbare Befehle ein." -#: src/iac_code/ui/repl.py:835 +#: src/iac_code/ui/repl.py:851 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ ruft nur Skills auf. Verwende stattdessen /{name}." -#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 +#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 #, python-brace-format msgid "Command error: {error}" msgstr "Befehlsfehler: {error}" -#: src/iac_code/ui/repl.py:864 +#: src/iac_code/ui/repl.py:880 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Kein Handler für Befehl: {name}" -#: src/iac_code/ui/repl.py:1126 +#: src/iac_code/ui/repl.py:1155 msgid "Goodbye!" msgstr "Auf Wiedersehen!" -#: src/iac_code/ui/repl.py:1127 +#: src/iac_code/ui/repl.py:1156 msgid "Resume this session with:" msgstr "Diese Sitzung fortsetzen mit:" -#: src/iac_code/ui/repl.py:1130 +#: src/iac_code/ui/repl.py:1159 msgid "Session ID" msgstr "Sitzungs-ID" -#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 +#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sitzung nicht gefunden: {session_id}" -#: src/iac_code/ui/repl.py:1233 +#: src/iac_code/ui/repl.py:1262 msgid "Session name: " msgstr "Sitzungsname: " -#: src/iac_code/ui/repl.py:1239 +#: src/iac_code/ui/repl.py:1268 msgid "Session name cannot be empty." msgstr "Der Sitzungsname darf nicht leer sein." -#: src/iac_code/ui/repl.py:1251 +#: src/iac_code/ui/repl.py:1280 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2444,23 +2444,23 @@ msgstr "" "Zum Fortsetzen ausführen:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1257 +#: src/iac_code/ui/repl.py:1286 msgid "Multiple sessions match. Resume one by ID:" msgstr "Mehrere Sitzungen passen. Setzen Sie eine per ID fort:" -#: src/iac_code/ui/repl.py:1370 +#: src/iac_code/ui/repl.py:1401 msgid "This conversation is from a different directory." msgstr "Diese Konversation stammt aus einem anderen Verzeichnis." -#: src/iac_code/ui/repl.py:1372 +#: src/iac_code/ui/repl.py:1403 msgid "To resume, run:" msgstr "Zum Fortsetzen ausführen:" -#: src/iac_code/ui/repl.py:1377 +#: src/iac_code/ui/repl.py:1408 msgid "(Command copied to clipboard)" msgstr "(Befehl in die Zwischenablage kopiert)" -#: src/iac_code/ui/repl.py:1534 +#: src/iac_code/ui/repl.py:1565 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2469,12 +2469,12 @@ msgstr "" "Das aktuelle Modell {model} unterstützt keine Bildeingabe. Verwenden Sie " "/model, um zu einem Vision-fähigen Modell zu wechseln." -#: src/iac_code/ui/repl.py:1543 +#: src/iac_code/ui/repl.py:1574 #, python-brace-format msgid "Image error: {err}" msgstr "Bildfehler: {err}" -#: src/iac_code/ui/repl.py:1560 +#: src/iac_code/ui/repl.py:1591 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 c26fb1f..51c1add 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 22:08+0800\n" +"POT-Creation-Date: 2026-06-03 11:39+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: es\n" @@ -130,7 +130,7 @@ msgstr "Uso: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "El gestor de memoria no está disponible." -#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:21 msgid "Usage: /rename " msgstr "Uso: /rename " @@ -138,18 +138,18 @@ msgstr "Uso: /rename " msgid "Rename is only available after a session is created." msgstr "El cambio de nombre solo está disponible después de crear una sesión." -#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:42 #, python-brace-format msgid "Session is already named {name}" msgstr "La sesión ya se llama {name}" -#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:43 #, python-brace-format msgid "Renamed session to {name}" msgstr "Sesión renombrada a {name}" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 +#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 msgid "Permission denied." msgstr "Permiso denegado." @@ -710,7 +710,7 @@ msgstr "Gestionar habilidades" msgid "Show current session status" msgstr "Mostrar el estado actual de la sesión" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1117 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navegar" @@ -718,8 +718,8 @@ msgstr "Navegar" #: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 #: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 #: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1117 +#: src/iac_code/commands/auth.py:1551 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmar" @@ -727,8 +727,8 @@ msgstr "Confirmar" #: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 #: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 #: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 -#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 -#: src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:1117 src/iac_code/commands/auth.py:1443 +#: src/iac_code/commands/auth.py:1551 msgid "Back" msgstr "Atrás" @@ -740,9 +740,9 @@ msgstr "Conservar" msgid "Re-enter" msgstr "Volver a introducir" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 -#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 -#: src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:863 +#: src/iac_code/commands/auth.py:921 src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:956 msgid " (current)" msgstr " (actual)" @@ -767,247 +767,247 @@ msgstr "Introduzca el nombre del modelo personalizado: " msgid "Error: console not available" msgstr "Error: la consola no está disponible" -#: src/iac_code/commands/auth.py:818 +#: src/iac_code/commands/auth.py:820 msgid "Configure LLM Provider" msgstr "Configurar proveedor LLM" -#: src/iac_code/commands/auth.py:819 +#: src/iac_code/commands/auth.py:821 msgid "Configure IaC Cloud Service" msgstr "Configurar servicio cloud IaC" -#: src/iac_code/commands/auth.py:821 +#: src/iac_code/commands/auth.py:823 msgid "Select configuration type" msgstr "Seleccionar tipo de configuración" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 -#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 -#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:825 src/iac_code/commands/auth.py:981 +#: src/iac_code/commands/auth.py:997 src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1490 src/iac_code/commands/auth.py:1531 msgid "Auth cancelled" msgstr "Autenticación cancelada" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 -#: src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:961 +#: src/iac_code/commands/auth.py:1051 #, python-brace-format msgid "Select provider — {group}" msgstr "Seleccionar proveedor — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:1036 msgid "Third-party" msgstr "Terceros" -#: src/iac_code/commands/auth.py:875 +#: src/iac_code/commands/auth.py:877 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 +#: src/iac_code/commands/auth.py:878 src/iac_code/commands/auth.py:1029 msgid "Configured" msgstr "Configurado" -#: src/iac_code/commands/auth.py:931 +#: src/iac_code/commands/auth.py:933 msgid "Select provider" msgstr "Seleccionar proveedor" -#: src/iac_code/commands/auth.py:972 +#: src/iac_code/commands/auth.py:974 #, python-brace-format msgid "Configure {provider}" msgstr "Configurar {provider}" -#: src/iac_code/commands/auth.py:988 +#: src/iac_code/commands/auth.py:990 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Introduzca la API key para {provider}" -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:1028 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 +#: src/iac_code/commands/auth.py:1037 src/iac_code/commands/auth.py:1058 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:427 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1037 +#: src/iac_code/commands/auth.py:1039 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1038 +#: src/iac_code/commands/auth.py:1040 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:429 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1042 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:420 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:418 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:419 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:422 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1047 src/iac_code/providers/registry.py:435 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1048 src/iac_code/providers/registry.py:434 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1047 +#: src/iac_code/commands/auth.py:1049 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1050 msgid "Compatible" msgstr "Compatible" -#: src/iac_code/commands/auth.py:1065 +#: src/iac_code/commands/auth.py:1067 msgid "Select Cloud Provider" msgstr "Seleccionar proveedor de cloud" -#: src/iac_code/commands/auth.py:1081 +#: src/iac_code/commands/auth.py:1083 msgid "Credential" msgstr "Credencial" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 -#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/auth.py:1084 src/iac_code/commands/auth.py:1199 +#: src/iac_code/commands/auth.py:1527 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Región" -#: src/iac_code/commands/auth.py:1084 +#: src/iac_code/commands/auth.py:1086 msgid "Configure Alibaba Cloud" msgstr "Configurar Alibaba Cloud" -#: src/iac_code/commands/auth.py:1186 +#: src/iac_code/commands/auth.py:1188 msgid "Current configuration" msgstr "Configuración actual" -#: src/iac_code/commands/auth.py:1188 +#: src/iac_code/commands/auth.py:1190 msgid "Mode" msgstr "Modo" -#: src/iac_code/commands/auth.py:1194 +#: src/iac_code/commands/auth.py:1196 msgid "(not set)" msgstr "(sin definir)" -#: src/iac_code/commands/auth.py:1203 +#: src/iac_code/commands/auth.py:1205 msgid "AccessKey" msgstr "AccessKey" -#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1207 src/iac_code/commands/auth.py:1219 msgid "STS Token" msgstr "Token STS" -#: src/iac_code/commands/auth.py:1207 +#: src/iac_code/commands/auth.py:1209 msgid "RAM Role" msgstr "Rol RAM" -#: src/iac_code/commands/auth.py:1209 +#: src/iac_code/commands/auth.py:1211 msgid "OAuth Login (Browser)" msgstr "Inicio de sesión OAuth (navegador)" -#: src/iac_code/commands/auth.py:1215 +#: src/iac_code/commands/auth.py:1217 msgid "AccessKey ID" msgstr "ID de AccessKey" -#: src/iac_code/commands/auth.py:1216 +#: src/iac_code/commands/auth.py:1218 msgid "AccessKey Secret" msgstr "Secreto de AccessKey" -#: src/iac_code/commands/auth.py:1218 +#: src/iac_code/commands/auth.py:1220 msgid "RAM Role ARN" msgstr "ARN del rol RAM" -#: src/iac_code/commands/auth.py:1219 +#: src/iac_code/commands/auth.py:1221 msgid "Session Name" msgstr "Nombre de sesión" -#: src/iac_code/commands/auth.py:1220 +#: src/iac_code/commands/auth.py:1222 msgid "OAuth Site Type" msgstr "Tipo de sitio OAuth" -#: src/iac_code/commands/auth.py:1221 +#: src/iac_code/commands/auth.py:1223 msgid "OAuth Access Token" msgstr "Token de acceso OAuth" -#: src/iac_code/commands/auth.py:1222 +#: src/iac_code/commands/auth.py:1224 msgid "OAuth Refresh Token" msgstr "Token de actualización OAuth" -#: src/iac_code/commands/auth.py:1223 +#: src/iac_code/commands/auth.py:1225 msgid "OAuth Access Token Expire" msgstr "Expiración del token de acceso OAuth" -#: src/iac_code/commands/auth.py:1224 +#: src/iac_code/commands/auth.py:1226 msgid "OAuth Refresh Token Expire" msgstr "Expiración del token de actualización OAuth" -#: src/iac_code/commands/auth.py:1225 +#: src/iac_code/commands/auth.py:1227 msgid "STS Expiration" msgstr "Expiración STS" -#: src/iac_code/commands/auth.py:1382 +#: src/iac_code/commands/auth.py:1384 msgid "China" msgstr "China" -#: src/iac_code/commands/auth.py:1383 +#: src/iac_code/commands/auth.py:1385 msgid "International" msgstr "Internacional" -#: src/iac_code/commands/auth.py:1385 +#: src/iac_code/commands/auth.py:1387 msgid "Choose site type" msgstr "Elegir tipo de sitio" -#: src/iac_code/commands/auth.py:1400 +#: src/iac_code/commands/auth.py:1402 #, python-brace-format msgid "Alibaba Cloud OAuth login failed: {error}" msgstr "Error de inicio de sesión OAuth de Alibaba Cloud: {error}" -#: src/iac_code/commands/auth.py:1416 +#: src/iac_code/commands/auth.py:1418 msgid "Configured: Alibaba Cloud OAuth credentials saved" msgstr "Configurado: credenciales OAuth de Alibaba Cloud guardadas" -#: src/iac_code/commands/auth.py:1428 +#: src/iac_code/commands/auth.py:1430 msgid "Configure Alibaba Cloud credentials" msgstr "Configurar credenciales de Alibaba Cloud" -#: src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1443 msgid "Reconfigure credential" msgstr "Reconfigurar la credencial" -#: src/iac_code/commands/auth.py:1454 +#: src/iac_code/commands/auth.py:1456 msgid "Select credential type" msgstr "Seleccionar tipo de credencial" -#: src/iac_code/commands/auth.py:1510 +#: src/iac_code/commands/auth.py:1512 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:1517 +#: src/iac_code/commands/auth.py:1519 msgid "Configure Alibaba Cloud region" msgstr "Configurar la región de Alibaba Cloud" -#: src/iac_code/commands/auth.py:1543 +#: src/iac_code/commands/auth.py:1545 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Configurado: región de Alibaba Cloud guardada en ~/.iac-code" @@ -1157,11 +1157,11 @@ msgstr "Modelo actual: {model}" msgid "Kept model as {model}" msgstr "Se mantiene el modelo como {model}" -#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +#: src/iac_code/commands/rename.py:16 src/iac_code/commands/rename.py:28 msgid "Rename is only available in interactive mode." msgstr "El cambio de nombre solo está disponible en modo interactivo." -#: src/iac_code/commands/rename.py:30 +#: src/iac_code/commands/rename.py:31 msgid "Rename cancelled" msgstr "Cambio de nombre cancelado" @@ -1365,79 +1365,79 @@ msgstr "" " correcta (actual: {base_url}). Muchos endpoints compatibles con OpenAI " "requieren el sufijo /v1 (p. ej., {base_url}/v1)." -#: src/iac_code/providers/registry.py:415 +#: src/iac_code/providers/registry.py:416 msgid "Alibaba Cloud Bailian" msgstr "Alibaba Cloud Bailian" -#: src/iac_code/providers/registry.py:416 +#: src/iac_code/providers/registry.py:417 msgid "Alibaba Cloud Bailian Token Plan" msgstr "Alibaba Cloud Bailian Token Plan" -#: src/iac_code/providers/registry.py:420 +#: src/iac_code/providers/registry.py:421 msgid "OpenAPI Compatible" msgstr "Compatible con OpenAPI" -#: src/iac_code/providers/registry.py:422 +#: src/iac_code/providers/registry.py:423 msgid "Kimi (China)" msgstr "Kimi (China)" -#: src/iac_code/providers/registry.py:423 +#: src/iac_code/providers/registry.py:424 msgid "Kimi (International)" msgstr "Kimi (Internacional)" -#: src/iac_code/providers/registry.py:424 +#: src/iac_code/providers/registry.py:425 msgid "MiniMax (China)" msgstr "MiniMax (China)" -#: src/iac_code/providers/registry.py:425 +#: src/iac_code/providers/registry.py:426 msgid "MiniMax (International)" msgstr "MiniMax (Internacional)" -#: src/iac_code/providers/registry.py:427 +#: src/iac_code/providers/registry.py:428 msgid "ZhiPu AI (International)" msgstr "ZhiPu AI (Internacional)" -#: src/iac_code/providers/registry.py:429 +#: src/iac_code/providers/registry.py:430 msgid "SiliconFlow (China)" msgstr "SiliconFlow (China)" -#: src/iac_code/providers/registry.py:430 +#: src/iac_code/providers/registry.py:431 msgid "SiliconFlow (International)" msgstr "SiliconFlow (Internacional)" -#: src/iac_code/providers/registry.py:431 +#: src/iac_code/providers/registry.py:432 msgid "Ollama (Local)" msgstr "Ollama (Local)" -#: src/iac_code/providers/registry.py:432 +#: src/iac_code/providers/registry.py:433 msgid "LM Studio (Local)" msgstr "LM Studio (Local)" -#: src/iac_code/providers/registry.py:435 +#: src/iac_code/providers/registry.py:436 msgid "ModelScope" msgstr "ModelScope" -#: src/iac_code/providers/registry.py:436 +#: src/iac_code/providers/registry.py:437 msgid "Alibaba Cloud CodingPlan" msgstr "Alibaba Cloud CodingPlan" -#: src/iac_code/providers/registry.py:437 +#: src/iac_code/providers/registry.py:438 msgid "Alibaba Cloud CodingPlan (International)" msgstr "Alibaba Cloud CodingPlan (Internacional)" -#: src/iac_code/providers/registry.py:438 +#: src/iac_code/providers/registry.py:439 msgid "ZhiPu AI CodingPlan" msgstr "ZhiPu AI CodingPlan" -#: src/iac_code/providers/registry.py:439 +#: src/iac_code/providers/registry.py:440 msgid "ZhiPu AI CodingPlan (International)" msgstr "ZhiPu AI CodingPlan (Internacional)" -#: src/iac_code/providers/registry.py:440 +#: src/iac_code/providers/registry.py:441 msgid "Volcengine CodingPlan" msgstr "Volcengine CodingPlan" -#: src/iac_code/providers/registry.py:441 +#: src/iac_code/providers/registry.py:442 msgid "Anthropic Compatible" msgstr "Compatible con Anthropic" @@ -1592,7 +1592,7 @@ msgstr "" "sesión de Alibaba Cloud e iníciela de nuevo si es necesario, y ejecute " "/auth para elegir de nuevo Inicio de sesión OAuth (navegador)." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "" @@ -2327,70 +2327,70 @@ msgstr "No, siempre denegar \"{rule}\" (esta sesión)" msgid "No, always reject this tool" msgstr "No, rechazar siempre esta herramienta" -#: src/iac_code/ui/repl.py:412 +#: src/iac_code/ui/repl.py:423 msgid "Press Ctrl+C again to exit." msgstr "Pulse Ctrl+C de nuevo para salir." -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:448 msgid "Interrupted." msgstr "Interrumpido." -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:507 msgid "Update now" msgstr "Actualizar ahora" -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:509 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Ejecuta el comando de actualización mostrado y sale cuando finalice " "correctamente." -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:512 msgid "Skip" msgstr "Omitir" -#: src/iac_code/ui/repl.py:503 +#: src/iac_code/ui/repl.py:514 msgid "Continue with the current version for this session." msgstr "Continuar con la versión actual durante esta sesión." -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:517 msgid "Skip until next version" msgstr "Omitir hasta la siguiente versión" -#: src/iac_code/ui/repl.py:508 +#: src/iac_code/ui/repl.py:519 msgid "Hide this update until a newer version is available." msgstr "" "Ocultar esta actualización hasta que haya una versión más nueva " "disponible." -#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 +#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 msgid "Update command failed. Continuing with the current version." msgstr "El comando de actualización falló. Se continuará con la versión actual." -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:543 msgid "Update completed. Restart iac-code to continue." msgstr "Actualización completada. Reinicia iac-code para continuar." -#: src/iac_code/ui/repl.py:570 +#: src/iac_code/ui/repl.py:581 msgid "No image in clipboard." msgstr "No hay ninguna imagen en el portapapeles." -#: src/iac_code/ui/repl.py:756 +#: src/iac_code/ui/repl.py:767 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:761 +#: src/iac_code/ui/repl.py:774 msgid "Shell command support is unavailable." msgstr "La compatibilidad con comandos de shell no está disponible." -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:844 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Habilidad desconocida: ${name}. Escribe / para listar comandos y " "habilidades." -#: src/iac_code/ui/repl.py:830 +#: src/iac_code/ui/repl.py:846 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" @@ -2398,47 +2398,47 @@ msgstr "" "command: /{name}. Type /help for available commands.Comando desconocido: " "/{name}. Escriba /help para ver los comandos disponibles." -#: src/iac_code/ui/repl.py:835 +#: src/iac_code/ui/repl.py:851 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ solo invoca habilidades. Usa /{name} en su lugar." -#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 +#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 #, python-brace-format msgid "Command error: {error}" msgstr "Error de comando: {error}" -#: src/iac_code/ui/repl.py:864 +#: src/iac_code/ui/repl.py:880 #, python-brace-format msgid "Command has no handler: {name}" msgstr "El comando no tiene controlador: {name}" -#: src/iac_code/ui/repl.py:1126 +#: src/iac_code/ui/repl.py:1155 msgid "Goodbye!" msgstr "¡Hasta luego!" -#: src/iac_code/ui/repl.py:1127 +#: src/iac_code/ui/repl.py:1156 msgid "Resume this session with:" msgstr "Para reanudar esta sesión, ejecute:" -#: src/iac_code/ui/repl.py:1130 +#: src/iac_code/ui/repl.py:1159 msgid "Session ID" msgstr "ID de sesión" -#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 +#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sesión no encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1233 +#: src/iac_code/ui/repl.py:1262 msgid "Session name: " msgstr "Nombre de sesión: " -#: src/iac_code/ui/repl.py:1239 +#: src/iac_code/ui/repl.py:1268 msgid "Session name cannot be empty." msgstr "El nombre de sesión no puede estar vacío." -#: src/iac_code/ui/repl.py:1251 +#: src/iac_code/ui/repl.py:1280 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2449,23 +2449,23 @@ msgstr "" "Para reanudar, ejecute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1257 +#: src/iac_code/ui/repl.py:1286 msgid "Multiple sessions match. Resume one by ID:" msgstr "Varias sesiones coinciden. Reanuda una por ID:" -#: src/iac_code/ui/repl.py:1370 +#: src/iac_code/ui/repl.py:1401 msgid "This conversation is from a different directory." msgstr "Esta conversación procede de otro directorio." -#: src/iac_code/ui/repl.py:1372 +#: src/iac_code/ui/repl.py:1403 msgid "To resume, run:" msgstr "Para reanudar, ejecute:" -#: src/iac_code/ui/repl.py:1377 +#: src/iac_code/ui/repl.py:1408 msgid "(Command copied to clipboard)" msgstr "(Comando copiado al portapapeles)" -#: src/iac_code/ui/repl.py:1534 +#: src/iac_code/ui/repl.py:1565 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2474,12 +2474,12 @@ msgstr "" "El modelo actual {model} no admite entrada de imágenes. Usa /model para " "cambiar a un modelo con capacidad de visión." -#: src/iac_code/ui/repl.py:1543 +#: src/iac_code/ui/repl.py:1574 #, python-brace-format msgid "Image error: {err}" msgstr "Error de imagen: {err}" -#: src/iac_code/ui/repl.py:1560 +#: src/iac_code/ui/repl.py:1591 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 0782765..a5bb319 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 22:08+0800\n" +"POT-Creation-Date: 2026-06-03 11:39+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: fr\n" @@ -127,7 +127,7 @@ msgstr "Utilisation : /debug [on|off]" msgid "Memory manager is unavailable." msgstr "Le gestionnaire de mémoire est indisponible." -#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:21 msgid "Usage: /rename " msgstr "Utilisation : /rename " @@ -135,18 +135,18 @@ msgstr "Utilisation : /rename " msgid "Rename is only available after a session is created." msgstr "Le renommage n'est disponible qu'après la création d'une session." -#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:42 #, python-brace-format msgid "Session is already named {name}" msgstr "La session porte déjà le nom {name}" -#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:43 #, python-brace-format msgid "Renamed session to {name}" msgstr "Session renommée en {name}" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 +#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 msgid "Permission denied." msgstr "Permission refusée." @@ -708,7 +708,7 @@ msgstr "Gérer les compétences" msgid "Show current session status" msgstr "Afficher l’état actuel de la session" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1117 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Naviguer" @@ -716,8 +716,8 @@ msgstr "Naviguer" #: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 #: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 #: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1117 +#: src/iac_code/commands/auth.py:1551 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmer" @@ -725,8 +725,8 @@ msgstr "Confirmer" #: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 #: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 #: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 -#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 -#: src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:1117 src/iac_code/commands/auth.py:1443 +#: src/iac_code/commands/auth.py:1551 msgid "Back" msgstr "Retour" @@ -738,9 +738,9 @@ msgstr "Conserver" msgid "Re-enter" msgstr "Saisir à nouveau" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 -#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 -#: src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:863 +#: src/iac_code/commands/auth.py:921 src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:956 msgid " (current)" msgstr " (actuel)" @@ -765,250 +765,250 @@ msgstr "Saisir le nom du modèle personnalisé : " msgid "Error: console not available" msgstr "Erreur : console indisponible" -#: src/iac_code/commands/auth.py:818 +#: src/iac_code/commands/auth.py:820 msgid "Configure LLM Provider" msgstr "Configurer le fournisseur LLM" -#: src/iac_code/commands/auth.py:819 +#: src/iac_code/commands/auth.py:821 msgid "Configure IaC Cloud Service" msgstr "Configurer le service cloud IaC" -#: src/iac_code/commands/auth.py:821 +#: src/iac_code/commands/auth.py:823 msgid "Select configuration type" msgstr "Sélectionner le type de configuration" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 -#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 -#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:825 src/iac_code/commands/auth.py:981 +#: src/iac_code/commands/auth.py:997 src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1490 src/iac_code/commands/auth.py:1531 msgid "Auth cancelled" msgstr "Authentification annulée" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 -#: src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:961 +#: src/iac_code/commands/auth.py:1051 #, python-brace-format msgid "Select provider — {group}" msgstr "Sélectionner le fournisseur — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:1036 msgid "Third-party" msgstr "Tiers" -#: src/iac_code/commands/auth.py:875 +#: src/iac_code/commands/auth.py:877 #, python-brace-format msgid "{status}: {provider}" msgstr "{status} : {provider}" -#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 +#: src/iac_code/commands/auth.py:878 src/iac_code/commands/auth.py:1029 msgid "Configured" msgstr "Configuré" -#: src/iac_code/commands/auth.py:931 +#: src/iac_code/commands/auth.py:933 msgid "Select provider" msgstr "Sélectionner le fournisseur" -#: src/iac_code/commands/auth.py:972 +#: src/iac_code/commands/auth.py:974 #, python-brace-format msgid "Configure {provider}" msgstr "Configurer {provider}" -#: src/iac_code/commands/auth.py:988 +#: src/iac_code/commands/auth.py:990 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Saisir la clé API pour {provider}" -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:1028 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status} : {provider} / {model}" -#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 +#: src/iac_code/commands/auth.py:1037 src/iac_code/commands/auth.py:1058 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:427 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1037 +#: src/iac_code/commands/auth.py:1039 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1038 +#: src/iac_code/commands/auth.py:1040 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:429 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1042 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:420 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:418 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:419 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:422 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1047 src/iac_code/providers/registry.py:435 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1048 src/iac_code/providers/registry.py:434 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1047 +#: src/iac_code/commands/auth.py:1049 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1050 msgid "Compatible" msgstr "Compatible" -#: src/iac_code/commands/auth.py:1065 +#: src/iac_code/commands/auth.py:1067 msgid "Select Cloud Provider" msgstr "Sélectionner le fournisseur cloud" -#: src/iac_code/commands/auth.py:1081 +#: src/iac_code/commands/auth.py:1083 msgid "Credential" msgstr "Identifiants" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 -#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/auth.py:1084 src/iac_code/commands/auth.py:1199 +#: src/iac_code/commands/auth.py:1527 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Région" -#: src/iac_code/commands/auth.py:1084 +#: src/iac_code/commands/auth.py:1086 msgid "Configure Alibaba Cloud" msgstr "Configurer Alibaba Cloud" -#: src/iac_code/commands/auth.py:1186 +#: src/iac_code/commands/auth.py:1188 msgid "Current configuration" msgstr "Configuration actuelle" -#: src/iac_code/commands/auth.py:1188 +#: src/iac_code/commands/auth.py:1190 msgid "Mode" msgstr "Mode" -#: src/iac_code/commands/auth.py:1194 +#: src/iac_code/commands/auth.py:1196 msgid "(not set)" msgstr "(non défini)" -#: src/iac_code/commands/auth.py:1203 +#: src/iac_code/commands/auth.py:1205 msgid "AccessKey" msgstr "AccessKey" -#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1207 src/iac_code/commands/auth.py:1219 msgid "STS Token" msgstr "Jeton STS" -#: src/iac_code/commands/auth.py:1207 +#: src/iac_code/commands/auth.py:1209 msgid "RAM Role" msgstr "Rôle RAM" -#: src/iac_code/commands/auth.py:1209 +#: src/iac_code/commands/auth.py:1211 msgid "OAuth Login (Browser)" msgstr "Connexion OAuth (navigateur)" -#: src/iac_code/commands/auth.py:1215 +#: src/iac_code/commands/auth.py:1217 msgid "AccessKey ID" msgstr "ID AccessKey" -#: src/iac_code/commands/auth.py:1216 +#: src/iac_code/commands/auth.py:1218 msgid "AccessKey Secret" msgstr "Secret AccessKey" -#: src/iac_code/commands/auth.py:1218 +#: src/iac_code/commands/auth.py:1220 msgid "RAM Role ARN" msgstr "ARN du rôle RAM" -#: src/iac_code/commands/auth.py:1219 +#: src/iac_code/commands/auth.py:1221 msgid "Session Name" msgstr "Nom de session" -#: src/iac_code/commands/auth.py:1220 +#: src/iac_code/commands/auth.py:1222 msgid "OAuth Site Type" msgstr "Type de site OAuth" -#: src/iac_code/commands/auth.py:1221 +#: src/iac_code/commands/auth.py:1223 msgid "OAuth Access Token" msgstr "Jeton d'accès OAuth" -#: src/iac_code/commands/auth.py:1222 +#: src/iac_code/commands/auth.py:1224 msgid "OAuth Refresh Token" msgstr "Jeton de rafraîchissement OAuth" -#: src/iac_code/commands/auth.py:1223 +#: src/iac_code/commands/auth.py:1225 msgid "OAuth Access Token Expire" msgstr "Expiration du jeton d'accès OAuth" -#: src/iac_code/commands/auth.py:1224 +#: src/iac_code/commands/auth.py:1226 msgid "OAuth Refresh Token Expire" msgstr "Expiration du jeton de rafraîchissement OAuth" -#: src/iac_code/commands/auth.py:1225 +#: src/iac_code/commands/auth.py:1227 msgid "STS Expiration" msgstr "Expiration STS" -#: src/iac_code/commands/auth.py:1382 +#: src/iac_code/commands/auth.py:1384 msgid "China" msgstr "Chine" -#: src/iac_code/commands/auth.py:1383 +#: src/iac_code/commands/auth.py:1385 msgid "International" msgstr "International" -#: src/iac_code/commands/auth.py:1385 +#: src/iac_code/commands/auth.py:1387 msgid "Choose site type" msgstr "Choisir le type de site" -#: src/iac_code/commands/auth.py:1400 +#: src/iac_code/commands/auth.py:1402 #, python-brace-format msgid "Alibaba Cloud OAuth login failed: {error}" msgstr "Échec de la connexion OAuth Alibaba Cloud : {error}" -#: src/iac_code/commands/auth.py:1416 +#: src/iac_code/commands/auth.py:1418 msgid "Configured: Alibaba Cloud OAuth credentials saved" msgstr "Configuré : identifiants OAuth Alibaba Cloud enregistrés" -#: src/iac_code/commands/auth.py:1428 +#: src/iac_code/commands/auth.py:1430 msgid "Configure Alibaba Cloud credentials" msgstr "Configurer les identifiants Alibaba Cloud" -#: src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1443 msgid "Reconfigure credential" msgstr "Reconfigurer les identifiants" -#: src/iac_code/commands/auth.py:1454 +#: src/iac_code/commands/auth.py:1456 msgid "Select credential type" msgstr "Sélectionner le type d’identifiants" -#: src/iac_code/commands/auth.py:1510 +#: src/iac_code/commands/auth.py:1512 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:1517 +#: src/iac_code/commands/auth.py:1519 msgid "Configure Alibaba Cloud region" msgstr "Configurer la région Alibaba Cloud" -#: src/iac_code/commands/auth.py:1543 +#: src/iac_code/commands/auth.py:1545 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "" "Configured: Alibaba Cloud region saved to ~/.iac-codeConfigured: Alibaba " @@ -1161,11 +1161,11 @@ msgstr "Modèle actuel : {model}" msgid "Kept model as {model}" msgstr "Modèle conservé : {model}" -#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +#: src/iac_code/commands/rename.py:16 src/iac_code/commands/rename.py:28 msgid "Rename is only available in interactive mode." msgstr "Le renommage n'est disponible qu'en mode interactif." -#: src/iac_code/commands/rename.py:30 +#: src/iac_code/commands/rename.py:31 msgid "Rename cancelled" msgstr "Renommage annulé" @@ -1367,79 +1367,79 @@ msgstr "" " correcte (actuelle : {base_url}). De nombreux points de terminaison " "compatibles OpenAI exigent le suffixe /v1 (p. ex. {base_url}/v1)." -#: src/iac_code/providers/registry.py:415 +#: src/iac_code/providers/registry.py:416 msgid "Alibaba Cloud Bailian" msgstr "Alibaba Cloud Bailian" -#: src/iac_code/providers/registry.py:416 +#: src/iac_code/providers/registry.py:417 msgid "Alibaba Cloud Bailian Token Plan" msgstr "Alibaba Cloud Bailian Token Plan" -#: src/iac_code/providers/registry.py:420 +#: src/iac_code/providers/registry.py:421 msgid "OpenAPI Compatible" msgstr "Compatible OpenAPI" -#: src/iac_code/providers/registry.py:422 +#: src/iac_code/providers/registry.py:423 msgid "Kimi (China)" msgstr "Kimi (Chine)" -#: src/iac_code/providers/registry.py:423 +#: src/iac_code/providers/registry.py:424 msgid "Kimi (International)" msgstr "Kimi (International)" -#: src/iac_code/providers/registry.py:424 +#: src/iac_code/providers/registry.py:425 msgid "MiniMax (China)" msgstr "MiniMax (Chine)" -#: src/iac_code/providers/registry.py:425 +#: src/iac_code/providers/registry.py:426 msgid "MiniMax (International)" msgstr "MiniMax (International)" -#: src/iac_code/providers/registry.py:427 +#: src/iac_code/providers/registry.py:428 msgid "ZhiPu AI (International)" msgstr "ZhiPu AI (International)" -#: src/iac_code/providers/registry.py:429 +#: src/iac_code/providers/registry.py:430 msgid "SiliconFlow (China)" msgstr "SiliconFlow (Chine)" -#: src/iac_code/providers/registry.py:430 +#: src/iac_code/providers/registry.py:431 msgid "SiliconFlow (International)" msgstr "SiliconFlow (International)" -#: src/iac_code/providers/registry.py:431 +#: src/iac_code/providers/registry.py:432 msgid "Ollama (Local)" msgstr "Ollama (Local)" -#: src/iac_code/providers/registry.py:432 +#: src/iac_code/providers/registry.py:433 msgid "LM Studio (Local)" msgstr "LM Studio (Local)" -#: src/iac_code/providers/registry.py:435 +#: src/iac_code/providers/registry.py:436 msgid "ModelScope" msgstr "ModelScope" -#: src/iac_code/providers/registry.py:436 +#: src/iac_code/providers/registry.py:437 msgid "Alibaba Cloud CodingPlan" msgstr "Alibaba Cloud CodingPlan" -#: src/iac_code/providers/registry.py:437 +#: src/iac_code/providers/registry.py:438 msgid "Alibaba Cloud CodingPlan (International)" msgstr "Alibaba Cloud CodingPlan (International)" -#: src/iac_code/providers/registry.py:438 +#: src/iac_code/providers/registry.py:439 msgid "ZhiPu AI CodingPlan" msgstr "ZhiPu AI CodingPlan" -#: src/iac_code/providers/registry.py:439 +#: src/iac_code/providers/registry.py:440 msgid "ZhiPu AI CodingPlan (International)" msgstr "ZhiPu AI CodingPlan (International)" -#: src/iac_code/providers/registry.py:440 +#: src/iac_code/providers/registry.py:441 msgid "Volcengine CodingPlan" msgstr "Volcengine CodingPlan" -#: src/iac_code/providers/registry.py:441 +#: src/iac_code/providers/registry.py:442 msgid "Anthropic Compatible" msgstr "Compatible Anthropic" @@ -1598,7 +1598,7 @@ msgstr "" " si nécessaire, puis exécutez /auth pour choisir de nouveau Connexion " "OAuth (navigateur)." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "La compétence « {name} » est désactivée. Exécutez /skills pour l'activer." @@ -2332,115 +2332,115 @@ msgstr "Non, toujours refuser \"{rule}\" (cette session)" msgid "No, always reject this tool" msgstr "Non, toujours refuser cet outil" -#: src/iac_code/ui/repl.py:412 +#: src/iac_code/ui/repl.py:423 msgid "Press Ctrl+C again to exit." msgstr "Appuyez de nouveau sur Ctrl+C pour quitter." -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:448 msgid "Interrupted." msgstr "Interrompu." -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:507 msgid "Update now" msgstr "Mettre à jour maintenant" -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:509 msgid "Run the shown update command and exit when it succeeds." msgstr "Exécute la commande de mise à jour affichée et quitte en cas de succès." -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:512 msgid "Skip" msgstr "Ignorer" -#: src/iac_code/ui/repl.py:503 +#: src/iac_code/ui/repl.py:514 msgid "Continue with the current version for this session." msgstr "Continuer avec la version actuelle pour cette session." -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:517 msgid "Skip until next version" msgstr "Ignorer jusqu’à la prochaine version" -#: src/iac_code/ui/repl.py:508 +#: src/iac_code/ui/repl.py:519 msgid "Hide this update until a newer version is available." msgstr "" "Masquer cette mise à jour jusqu’à ce qu’une version plus récente soit " "disponible." -#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 +#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 msgid "Update command failed. Continuing with the current version." msgstr "La commande de mise à jour a échoué. La version actuelle sera conservée." -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:543 msgid "Update completed. Restart iac-code to continue." msgstr "Mise à jour terminée. Redémarrez iac-code pour continuer." -#: src/iac_code/ui/repl.py:570 +#: src/iac_code/ui/repl.py:581 msgid "No image in clipboard." msgstr "Aucune image dans le presse-papiers." -#: src/iac_code/ui/repl.py:756 +#: src/iac_code/ui/repl.py:767 msgid "Usage: !" msgstr "Utilisation : !" -#: src/iac_code/ui/repl.py:761 +#: src/iac_code/ui/repl.py:774 msgid "Shell command support is unavailable." msgstr "La prise en charge des commandes shell n'est pas disponible." -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:844 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Compétence inconnue : ${name}. Tapez / pour lister les commandes et les " "compétences." -#: src/iac_code/ui/repl.py:830 +#: src/iac_code/ui/repl.py:846 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available commands.Commande " "inconnue : /{name}. Saisissez /help pour la liste des commandes." -#: src/iac_code/ui/repl.py:835 +#: src/iac_code/ui/repl.py:851 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ n'invoque que des compétences. Utilisez plutôt /{name}." -#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 +#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 #, python-brace-format msgid "Command error: {error}" msgstr "Erreur de commande : {error}" -#: src/iac_code/ui/repl.py:864 +#: src/iac_code/ui/repl.py:880 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Aucun gestionnaire pour la commande : {name}" -#: src/iac_code/ui/repl.py:1126 +#: src/iac_code/ui/repl.py:1155 msgid "Goodbye!" msgstr "Au revoir !" -#: src/iac_code/ui/repl.py:1127 +#: src/iac_code/ui/repl.py:1156 msgid "Resume this session with:" msgstr "Pour reprendre cette session :" -#: src/iac_code/ui/repl.py:1130 +#: src/iac_code/ui/repl.py:1159 msgid "Session ID" msgstr "ID de session" -#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 +#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Session introuvable : {session_id}" -#: src/iac_code/ui/repl.py:1233 +#: src/iac_code/ui/repl.py:1262 msgid "Session name: " msgstr "Nom de session : " -#: src/iac_code/ui/repl.py:1239 +#: src/iac_code/ui/repl.py:1268 msgid "Session name cannot be empty." msgstr "Le nom de session ne peut pas être vide." -#: src/iac_code/ui/repl.py:1251 +#: src/iac_code/ui/repl.py:1280 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2451,23 +2451,23 @@ msgstr "" "Pour la reprendre, exécutez :\n" " {cmd}" -#: src/iac_code/ui/repl.py:1257 +#: src/iac_code/ui/repl.py:1286 msgid "Multiple sessions match. Resume one by ID:" msgstr "Plusieurs sessions correspondent. Reprenez-en une par ID :" -#: src/iac_code/ui/repl.py:1370 +#: src/iac_code/ui/repl.py:1401 msgid "This conversation is from a different directory." msgstr "Cette conversation provient d’un autre répertoire." -#: src/iac_code/ui/repl.py:1372 +#: src/iac_code/ui/repl.py:1403 msgid "To resume, run:" msgstr "Pour reprendre, exécutez :" -#: src/iac_code/ui/repl.py:1377 +#: src/iac_code/ui/repl.py:1408 msgid "(Command copied to clipboard)" msgstr "(Commande copiée dans le presse-papiers)" -#: src/iac_code/ui/repl.py:1534 +#: src/iac_code/ui/repl.py:1565 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2476,12 +2476,12 @@ msgstr "" "Le modèle actuel {model} ne prend pas en charge l’entrée d’image. " "Utilisez /model pour passer à un modèle compatible vision." -#: src/iac_code/ui/repl.py:1543 +#: src/iac_code/ui/repl.py:1574 #, python-brace-format msgid "Image error: {err}" msgstr "Erreur d’image : {err}" -#: src/iac_code/ui/repl.py:1560 +#: src/iac_code/ui/repl.py:1591 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 e0ab65b..d0c70aa 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 22:08+0800\n" +"POT-Creation-Date: 2026-06-03 11:39+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: ja\n" @@ -121,7 +121,7 @@ msgstr "使用方法:/debug [on|off]" msgid "Memory manager is unavailable." msgstr "メモリマネージャーを利用できません。" -#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:21 msgid "Usage: /rename " msgstr "使用法: /rename <名前>" @@ -129,18 +129,18 @@ msgstr "使用法: /rename <名前>" msgid "Rename is only available after a session is created." msgstr "セッション作成後にのみ名前を変更できます。" -#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:42 #, python-brace-format msgid "Session is already named {name}" msgstr "セッション名はすでに {name} です" -#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:43 #, python-brace-format msgid "Renamed session to {name}" msgstr "セッション名を {name} に変更しました" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 +#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 msgid "Permission denied." msgstr "権限が拒否されました。" @@ -684,7 +684,7 @@ msgstr "スキルを管理" msgid "Show current session status" msgstr "現在のセッション状態を表示" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1117 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "移動" @@ -692,8 +692,8 @@ msgstr "移動" #: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 #: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 #: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1117 +#: src/iac_code/commands/auth.py:1551 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "確認" @@ -701,8 +701,8 @@ msgstr "確認" #: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 #: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 #: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 -#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 -#: src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:1117 src/iac_code/commands/auth.py:1443 +#: src/iac_code/commands/auth.py:1551 msgid "Back" msgstr "戻る" @@ -714,9 +714,9 @@ msgstr "維持" msgid "Re-enter" msgstr "再入力" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 -#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 -#: src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:863 +#: src/iac_code/commands/auth.py:921 src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:956 msgid " (current)" msgstr " (現在)" @@ -741,249 +741,249 @@ msgstr "カスタムモデル名を入力してください:" msgid "Error: console not available" msgstr "エラー:コンソールを使用できません" -#: src/iac_code/commands/auth.py:818 +#: src/iac_code/commands/auth.py:820 msgid "Configure LLM Provider" msgstr "LLM プロバイダーを設定" -#: src/iac_code/commands/auth.py:819 +#: src/iac_code/commands/auth.py:821 msgid "Configure IaC Cloud Service" msgstr "IaC クラウドサービスを設定" -#: src/iac_code/commands/auth.py:821 +#: src/iac_code/commands/auth.py:823 msgid "Select configuration type" msgstr "設定の種類を選択" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 -#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 -#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:825 src/iac_code/commands/auth.py:981 +#: src/iac_code/commands/auth.py:997 src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1490 src/iac_code/commands/auth.py:1531 msgid "Auth cancelled" msgstr "認証をキャンセルしました" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 -#: src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:961 +#: src/iac_code/commands/auth.py:1051 #, python-brace-format msgid "Select provider — {group}" msgstr "プロバイダーを選択 — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:1036 msgid "Third-party" msgstr "サードパーティ" -#: src/iac_code/commands/auth.py:875 +#: src/iac_code/commands/auth.py:877 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}:{provider}" -#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 +#: src/iac_code/commands/auth.py:878 src/iac_code/commands/auth.py:1029 msgid "Configured" msgstr "設定済み" -#: src/iac_code/commands/auth.py:931 +#: src/iac_code/commands/auth.py:933 msgid "Select provider" msgstr "プロバイダーを選択" -#: src/iac_code/commands/auth.py:972 +#: src/iac_code/commands/auth.py:974 #, python-brace-format msgid "Configure {provider}" msgstr "{provider} を設定" -#: src/iac_code/commands/auth.py:988 +#: src/iac_code/commands/auth.py:990 #, python-brace-format msgid "Enter API key for {provider}" msgstr "{provider} の API key を入力してください" -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:1028 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}:{provider} / {model}" -#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 +#: src/iac_code/commands/auth.py:1037 src/iac_code/commands/auth.py:1058 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:427 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1037 +#: src/iac_code/commands/auth.py:1039 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1038 +#: src/iac_code/commands/auth.py:1040 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:429 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1042 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:420 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:418 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:419 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:422 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1047 src/iac_code/providers/registry.py:435 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1048 src/iac_code/providers/registry.py:434 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1047 +#: src/iac_code/commands/auth.py:1049 msgid "Local" msgstr "ローカル" -#: src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1050 msgid "Compatible" msgstr "互換モード" -#: src/iac_code/commands/auth.py:1065 +#: src/iac_code/commands/auth.py:1067 msgid "Select Cloud Provider" msgstr "クラウドプロバイダーを選択" -#: src/iac_code/commands/auth.py:1081 +#: src/iac_code/commands/auth.py:1083 msgid "Credential" msgstr "クレデンシャル" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 -#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/auth.py:1084 src/iac_code/commands/auth.py:1199 +#: src/iac_code/commands/auth.py:1527 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "リージョン" -#: src/iac_code/commands/auth.py:1084 +#: src/iac_code/commands/auth.py:1086 msgid "Configure Alibaba Cloud" msgstr "Alibaba Cloud を設定" -#: src/iac_code/commands/auth.py:1186 +#: src/iac_code/commands/auth.py:1188 msgid "Current configuration" msgstr "現在の設定" -#: src/iac_code/commands/auth.py:1188 +#: src/iac_code/commands/auth.py:1190 msgid "Mode" msgstr "モード" -#: src/iac_code/commands/auth.py:1194 +#: src/iac_code/commands/auth.py:1196 msgid "(not set)" msgstr "(未設定)" -#: src/iac_code/commands/auth.py:1203 +#: src/iac_code/commands/auth.py:1205 msgid "AccessKey" msgstr "AccessKey" -#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1207 src/iac_code/commands/auth.py:1219 msgid "STS Token" msgstr "STS トークン" -#: src/iac_code/commands/auth.py:1207 +#: src/iac_code/commands/auth.py:1209 msgid "RAM Role" msgstr "RAM ロール" -#: src/iac_code/commands/auth.py:1209 +#: src/iac_code/commands/auth.py:1211 msgid "OAuth Login (Browser)" msgstr "OAuth ログイン(ブラウザー)" -#: src/iac_code/commands/auth.py:1215 +#: src/iac_code/commands/auth.py:1217 msgid "AccessKey ID" msgstr "AccessKey ID" -#: src/iac_code/commands/auth.py:1216 +#: src/iac_code/commands/auth.py:1218 msgid "AccessKey Secret" msgstr "AccessKey シークレット" -#: src/iac_code/commands/auth.py:1218 +#: src/iac_code/commands/auth.py:1220 msgid "RAM Role ARN" msgstr "RAM ロール ARN" -#: src/iac_code/commands/auth.py:1219 +#: src/iac_code/commands/auth.py:1221 msgid "Session Name" msgstr "セッション名" -#: src/iac_code/commands/auth.py:1220 +#: src/iac_code/commands/auth.py:1222 msgid "OAuth Site Type" msgstr "OAuth サイトタイプ" -#: src/iac_code/commands/auth.py:1221 +#: src/iac_code/commands/auth.py:1223 msgid "OAuth Access Token" msgstr "OAuth アクセストークン" -#: src/iac_code/commands/auth.py:1222 +#: src/iac_code/commands/auth.py:1224 msgid "OAuth Refresh Token" msgstr "OAuth リフレッシュトークン" -#: src/iac_code/commands/auth.py:1223 +#: src/iac_code/commands/auth.py:1225 msgid "OAuth Access Token Expire" msgstr "OAuth アクセストークンの有効期限" -#: src/iac_code/commands/auth.py:1224 +#: src/iac_code/commands/auth.py:1226 msgid "OAuth Refresh Token Expire" msgstr "OAuth リフレッシュトークンの有効期限" -#: src/iac_code/commands/auth.py:1225 +#: src/iac_code/commands/auth.py:1227 msgid "STS Expiration" msgstr "STS 有効期限" -#: src/iac_code/commands/auth.py:1382 +#: src/iac_code/commands/auth.py:1384 msgid "China" msgstr "中国" -#: src/iac_code/commands/auth.py:1383 +#: src/iac_code/commands/auth.py:1385 msgid "International" msgstr "国際" -#: src/iac_code/commands/auth.py:1385 +#: src/iac_code/commands/auth.py:1387 msgid "Choose site type" msgstr "サイトタイプを選択" -#: src/iac_code/commands/auth.py:1400 +#: src/iac_code/commands/auth.py:1402 #, python-brace-format msgid "Alibaba Cloud OAuth login failed: {error}" msgstr "Alibaba Cloud OAuth ログインに失敗しました: {error}" -#: src/iac_code/commands/auth.py:1416 +#: src/iac_code/commands/auth.py:1418 msgid "Configured: Alibaba Cloud OAuth credentials saved" msgstr "設定完了: Alibaba Cloud OAuth 認証情報を保存しました" -#: src/iac_code/commands/auth.py:1428 +#: src/iac_code/commands/auth.py:1430 msgid "Configure Alibaba Cloud credentials" msgstr "Alibaba Cloud のクレデンシャルを設定" -#: src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1443 msgid "Reconfigure credential" msgstr "クレデンシャルを再設定" -#: src/iac_code/commands/auth.py:1454 +#: src/iac_code/commands/auth.py:1456 msgid "Select credential type" msgstr "クレデンシャルの種類を選択" -#: src/iac_code/commands/auth.py:1510 +#: src/iac_code/commands/auth.py:1512 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:1517 +#: src/iac_code/commands/auth.py:1519 msgid "Configure Alibaba Cloud region" msgstr "Alibaba Cloud のリージョンを設定" -#: src/iac_code/commands/auth.py:1543 +#: src/iac_code/commands/auth.py:1545 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "設定しました:Alibaba Cloud のリージョンを ~/.iac-code に保存しました" @@ -1133,11 +1133,11 @@ msgstr "現在のモデル:{model}" msgid "Kept model as {model}" msgstr "モデルを {model} のままにしました" -#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +#: src/iac_code/commands/rename.py:16 src/iac_code/commands/rename.py:28 msgid "Rename is only available in interactive mode." msgstr "名前の変更は対話モードでのみ使用できます。" -#: src/iac_code/commands/rename.py:30 +#: src/iac_code/commands/rename.py:31 msgid "Rename cancelled" msgstr "名前の変更をキャンセルしました" @@ -1333,79 +1333,79 @@ msgstr "" "API から無効な応答が返りました。API Base URL が正しいか確認してください(現在:{base_url})。 多くの OpenAI " "互換エンドポイントでは /v1 接尾辞が必要です(例:{base_url}/v1)。" -#: src/iac_code/providers/registry.py:415 +#: src/iac_code/providers/registry.py:416 msgid "Alibaba Cloud Bailian" msgstr "Alibaba Cloud 百錬" -#: src/iac_code/providers/registry.py:416 +#: src/iac_code/providers/registry.py:417 msgid "Alibaba Cloud Bailian Token Plan" msgstr "Alibaba Cloud 百錬 Token Plan" -#: src/iac_code/providers/registry.py:420 +#: src/iac_code/providers/registry.py:421 msgid "OpenAPI Compatible" msgstr "OpenAPI 互換" -#: src/iac_code/providers/registry.py:422 +#: src/iac_code/providers/registry.py:423 msgid "Kimi (China)" msgstr "Kimi(中国版)" -#: src/iac_code/providers/registry.py:423 +#: src/iac_code/providers/registry.py:424 msgid "Kimi (International)" msgstr "Kimi(国際版)" -#: src/iac_code/providers/registry.py:424 +#: src/iac_code/providers/registry.py:425 msgid "MiniMax (China)" msgstr "MiniMax(中国版)" -#: src/iac_code/providers/registry.py:425 +#: src/iac_code/providers/registry.py:426 msgid "MiniMax (International)" msgstr "MiniMax(国際版)" -#: src/iac_code/providers/registry.py:427 +#: src/iac_code/providers/registry.py:428 msgid "ZhiPu AI (International)" msgstr "ZhiPu AI(国際版)" -#: src/iac_code/providers/registry.py:429 +#: src/iac_code/providers/registry.py:430 msgid "SiliconFlow (China)" msgstr "SiliconFlow(中国版)" -#: src/iac_code/providers/registry.py:430 +#: src/iac_code/providers/registry.py:431 msgid "SiliconFlow (International)" msgstr "SiliconFlow(国際版)" -#: src/iac_code/providers/registry.py:431 +#: src/iac_code/providers/registry.py:432 msgid "Ollama (Local)" msgstr "Ollama(ローカル)" -#: src/iac_code/providers/registry.py:432 +#: src/iac_code/providers/registry.py:433 msgid "LM Studio (Local)" msgstr "LM Studio(ローカル)" -#: src/iac_code/providers/registry.py:435 +#: src/iac_code/providers/registry.py:436 msgid "ModelScope" msgstr "ModelScope" -#: src/iac_code/providers/registry.py:436 +#: src/iac_code/providers/registry.py:437 msgid "Alibaba Cloud CodingPlan" msgstr "Alibaba Cloud CodingPlan" -#: src/iac_code/providers/registry.py:437 +#: src/iac_code/providers/registry.py:438 msgid "Alibaba Cloud CodingPlan (International)" msgstr "Alibaba Cloud CodingPlan(国際版)" -#: src/iac_code/providers/registry.py:438 +#: src/iac_code/providers/registry.py:439 msgid "ZhiPu AI CodingPlan" msgstr "ZhiPu AI CodingPlan" -#: src/iac_code/providers/registry.py:439 +#: src/iac_code/providers/registry.py:440 msgid "ZhiPu AI CodingPlan (International)" msgstr "ZhiPu AI CodingPlan(国際版)" -#: src/iac_code/providers/registry.py:440 +#: src/iac_code/providers/registry.py:441 msgid "Volcengine CodingPlan" msgstr "Volcengine CodingPlan" -#: src/iac_code/providers/registry.py:441 +#: src/iac_code/providers/registry.py:442 msgid "Anthropic Compatible" msgstr "Anthropic 互換" @@ -1555,7 +1555,7 @@ msgstr "" "ロールに割り当ててください。ユーザーグループはサポートされていません。その後、古い認可ページを閉じ、必要に応じて Alibaba Cloud " "からサインアウトしてサインインし直し、/auth を実行して OAuth ログイン(ブラウザー)を再度選択してください。" -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "スキル「{name}」は無効です。有効にするには /skills を実行してください。" @@ -2269,111 +2269,111 @@ msgstr "いいえ、常に \"{rule}\" を拒否(このセッション)" msgid "No, always reject this tool" msgstr "いいえ、このツールは常に拒否" -#: src/iac_code/ui/repl.py:412 +#: src/iac_code/ui/repl.py:423 msgid "Press Ctrl+C again to exit." msgstr "終了するには Ctrl+C をもう一度押してください。" -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:448 msgid "Interrupted." msgstr "中断しました。" -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:507 msgid "Update now" msgstr "今すぐ更新" -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:509 msgid "Run the shown update command and exit when it succeeds." msgstr "表示された更新コマンドを実行し、成功したら終了します。" -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:512 msgid "Skip" msgstr "スキップ" -#: src/iac_code/ui/repl.py:503 +#: src/iac_code/ui/repl.py:514 msgid "Continue with the current version for this session." msgstr "このセッションでは現在のバージョンを使い続けます。" -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:517 msgid "Skip until next version" msgstr "次のバージョンまでスキップ" -#: src/iac_code/ui/repl.py:508 +#: src/iac_code/ui/repl.py:519 msgid "Hide this update until a newer version is available." msgstr "より新しいバージョンが利用可能になるまで、この更新を非表示にします。" -#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 +#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 msgid "Update command failed. Continuing with the current version." msgstr "更新コマンドに失敗しました。現在のバージョンで続行します。" -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:543 msgid "Update completed. Restart iac-code to continue." msgstr "更新が完了しました。続行するには iac-code を再起動してください。" -#: src/iac_code/ui/repl.py:570 +#: src/iac_code/ui/repl.py:581 msgid "No image in clipboard." msgstr "クリップボードに画像がありません。" -#: src/iac_code/ui/repl.py:756 +#: src/iac_code/ui/repl.py:767 msgid "Usage: !" msgstr "使用方法: !" -#: src/iac_code/ui/repl.py:761 +#: src/iac_code/ui/repl.py:774 msgid "Shell command support is unavailable." msgstr "シェルコマンドのサポートは利用できません。" -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:844 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "不明なスキル: ${name}。/ を入力するとコマンドとスキルを一覧表示します。" -#: src/iac_code/ui/repl.py:830 +#: src/iac_code/ui/repl.py:846 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available " "commands.不明なコマンドです:/{name}。利用可能なコマンドは /help を入力してください。" -#: src/iac_code/ui/repl.py:835 +#: src/iac_code/ui/repl.py:851 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ はスキルのみを呼び出します。代わりに /{name} を使用してください。" -#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 +#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 #, python-brace-format msgid "Command error: {error}" msgstr "コマンドエラー:{error}" -#: src/iac_code/ui/repl.py:864 +#: src/iac_code/ui/repl.py:880 #, python-brace-format msgid "Command has no handler: {name}" msgstr "ハンドラーがないコマンドです:{name}" -#: src/iac_code/ui/repl.py:1126 +#: src/iac_code/ui/repl.py:1155 msgid "Goodbye!" msgstr "さようなら。" -#: src/iac_code/ui/repl.py:1127 +#: src/iac_code/ui/repl.py:1156 msgid "Resume this session with:" msgstr "このセッションを再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:1130 +#: src/iac_code/ui/repl.py:1159 msgid "Session ID" msgstr "セッション ID" -#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 +#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 #, python-brace-format msgid "Session not found: {session_id}" msgstr "セッションが見つかりません:{session_id}" -#: src/iac_code/ui/repl.py:1233 +#: src/iac_code/ui/repl.py:1262 msgid "Session name: " msgstr "セッション名: " -#: src/iac_code/ui/repl.py:1239 +#: src/iac_code/ui/repl.py:1268 msgid "Session name cannot be empty." msgstr "セッション名は空にできません。" -#: src/iac_code/ui/repl.py:1251 +#: src/iac_code/ui/repl.py:1280 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2384,35 +2384,35 @@ msgstr "" "再開するには次を実行してください:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1257 +#: src/iac_code/ui/repl.py:1286 msgid "Multiple sessions match. Resume one by ID:" msgstr "複数のセッションが一致しました。ID でいずれかを再開してください:" -#: src/iac_code/ui/repl.py:1370 +#: src/iac_code/ui/repl.py:1401 msgid "This conversation is from a different directory." msgstr "この会話は別のディレクトリ由来です。" -#: src/iac_code/ui/repl.py:1372 +#: src/iac_code/ui/repl.py:1403 msgid "To resume, run:" msgstr "再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:1377 +#: src/iac_code/ui/repl.py:1408 msgid "(Command copied to clipboard)" msgstr "(コマンドをクリップボードにコピーしました)" -#: src/iac_code/ui/repl.py:1534 +#: src/iac_code/ui/repl.py:1565 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " "to a vision-capable model." msgstr "現在のモデル {model} は画像入力をサポートしていません。/model を使用してビジョン対応モデルに切り替えてください。" -#: src/iac_code/ui/repl.py:1543 +#: src/iac_code/ui/repl.py:1574 #, python-brace-format msgid "Image error: {err}" msgstr "画像エラー:{err}" -#: src/iac_code/ui/repl.py:1560 +#: src/iac_code/ui/repl.py:1591 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 6c2494a..4186954 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 22:08+0800\n" +"POT-Creation-Date: 2026-06-03 11:39+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: pt\n" @@ -127,7 +127,7 @@ msgstr "Uso: /debug [on|off]" msgid "Memory manager is unavailable." msgstr "O gerenciador de memória está indisponível." -#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:21 msgid "Usage: /rename " msgstr "Uso: /rename " @@ -135,18 +135,18 @@ msgstr "Uso: /rename " msgid "Rename is only available after a session is created." msgstr "Renomear só fica disponível depois que uma sessão é criada." -#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:42 #, python-brace-format msgid "Session is already named {name}" msgstr "A sessão já se chama {name}" -#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:43 #, python-brace-format msgid "Renamed session to {name}" msgstr "Sessão renomeada para {name}" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 +#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 msgid "Permission denied." msgstr "Permissão negada." @@ -704,7 +704,7 @@ msgstr "Gerenciar habilidades" msgid "Show current session status" msgstr "Mostrar o status atual da sessão" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1117 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "Navegar" @@ -712,8 +712,8 @@ msgstr "Navegar" #: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 #: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 #: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1117 +#: src/iac_code/commands/auth.py:1551 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "Confirmar" @@ -721,8 +721,8 @@ msgstr "Confirmar" #: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 #: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 #: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 -#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 -#: src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:1117 src/iac_code/commands/auth.py:1443 +#: src/iac_code/commands/auth.py:1551 msgid "Back" msgstr "Voltar" @@ -734,9 +734,9 @@ msgstr "Manter" msgid "Re-enter" msgstr "Digitar novamente" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 -#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 -#: src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:863 +#: src/iac_code/commands/auth.py:921 src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:956 msgid " (current)" msgstr " (atual)" @@ -761,247 +761,247 @@ msgstr "Informe o nome do modelo personalizado: " msgid "Error: console not available" msgstr "Erro: console indisponível" -#: src/iac_code/commands/auth.py:818 +#: src/iac_code/commands/auth.py:820 msgid "Configure LLM Provider" msgstr "Configurar provedor LLM" -#: src/iac_code/commands/auth.py:819 +#: src/iac_code/commands/auth.py:821 msgid "Configure IaC Cloud Service" msgstr "Configurar serviço de nuvem IaC" -#: src/iac_code/commands/auth.py:821 +#: src/iac_code/commands/auth.py:823 msgid "Select configuration type" msgstr "Selecionar tipo de configuração" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 -#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 -#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:825 src/iac_code/commands/auth.py:981 +#: src/iac_code/commands/auth.py:997 src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1490 src/iac_code/commands/auth.py:1531 msgid "Auth cancelled" msgstr "Autenticação cancelada" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 -#: src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:961 +#: src/iac_code/commands/auth.py:1051 #, python-brace-format msgid "Select provider — {group}" msgstr "Selecionar provedor — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:1036 msgid "Third-party" msgstr "Terceiros" -#: src/iac_code/commands/auth.py:875 +#: src/iac_code/commands/auth.py:877 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}: {provider}" -#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 +#: src/iac_code/commands/auth.py:878 src/iac_code/commands/auth.py:1029 msgid "Configured" msgstr "Configurado" -#: src/iac_code/commands/auth.py:931 +#: src/iac_code/commands/auth.py:933 msgid "Select provider" msgstr "Selecionar provedor" -#: src/iac_code/commands/auth.py:972 +#: src/iac_code/commands/auth.py:974 #, python-brace-format msgid "Configure {provider}" msgstr "Configurar {provider}" -#: src/iac_code/commands/auth.py:988 +#: src/iac_code/commands/auth.py:990 #, python-brace-format msgid "Enter API key for {provider}" msgstr "Informe a API key para {provider}" -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:1028 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}: {provider} / {model}" -#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 +#: src/iac_code/commands/auth.py:1037 src/iac_code/commands/auth.py:1058 msgid "Alibaba Cloud" msgstr "Alibaba Cloud" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:427 msgid "ZhiPu AI" msgstr "ZhiPu AI" -#: src/iac_code/commands/auth.py:1037 +#: src/iac_code/commands/auth.py:1039 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1038 +#: src/iac_code/commands/auth.py:1040 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:429 msgid "Volcengine" msgstr "Volcengine" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1042 msgid "SiliconFlow" msgstr "SiliconFlow" -#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:420 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:418 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:419 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:422 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1047 src/iac_code/providers/registry.py:435 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1048 src/iac_code/providers/registry.py:434 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1047 +#: src/iac_code/commands/auth.py:1049 msgid "Local" msgstr "Local" -#: src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1050 msgid "Compatible" msgstr "Compatível" -#: src/iac_code/commands/auth.py:1065 +#: src/iac_code/commands/auth.py:1067 msgid "Select Cloud Provider" msgstr "Selecionar provedor de nuvem" -#: src/iac_code/commands/auth.py:1081 +#: src/iac_code/commands/auth.py:1083 msgid "Credential" msgstr "Credencial" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 -#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/auth.py:1084 src/iac_code/commands/auth.py:1199 +#: src/iac_code/commands/auth.py:1527 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "Região" -#: src/iac_code/commands/auth.py:1084 +#: src/iac_code/commands/auth.py:1086 msgid "Configure Alibaba Cloud" msgstr "Configurar Alibaba Cloud" -#: src/iac_code/commands/auth.py:1186 +#: src/iac_code/commands/auth.py:1188 msgid "Current configuration" msgstr "Configuração atual" -#: src/iac_code/commands/auth.py:1188 +#: src/iac_code/commands/auth.py:1190 msgid "Mode" msgstr "Modo" -#: src/iac_code/commands/auth.py:1194 +#: src/iac_code/commands/auth.py:1196 msgid "(not set)" msgstr "(não definido)" -#: src/iac_code/commands/auth.py:1203 +#: src/iac_code/commands/auth.py:1205 msgid "AccessKey" msgstr "AccessKey" -#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1207 src/iac_code/commands/auth.py:1219 msgid "STS Token" msgstr "Token STS" -#: src/iac_code/commands/auth.py:1207 +#: src/iac_code/commands/auth.py:1209 msgid "RAM Role" msgstr "Função RAM" -#: src/iac_code/commands/auth.py:1209 +#: src/iac_code/commands/auth.py:1211 msgid "OAuth Login (Browser)" msgstr "Login OAuth (navegador)" -#: src/iac_code/commands/auth.py:1215 +#: src/iac_code/commands/auth.py:1217 msgid "AccessKey ID" msgstr "ID da AccessKey" -#: src/iac_code/commands/auth.py:1216 +#: src/iac_code/commands/auth.py:1218 msgid "AccessKey Secret" msgstr "Segredo da AccessKey" -#: src/iac_code/commands/auth.py:1218 +#: src/iac_code/commands/auth.py:1220 msgid "RAM Role ARN" msgstr "ARN da função RAM" -#: src/iac_code/commands/auth.py:1219 +#: src/iac_code/commands/auth.py:1221 msgid "Session Name" msgstr "Nome da sessão" -#: src/iac_code/commands/auth.py:1220 +#: src/iac_code/commands/auth.py:1222 msgid "OAuth Site Type" msgstr "Tipo de site OAuth" -#: src/iac_code/commands/auth.py:1221 +#: src/iac_code/commands/auth.py:1223 msgid "OAuth Access Token" msgstr "Token de acesso OAuth" -#: src/iac_code/commands/auth.py:1222 +#: src/iac_code/commands/auth.py:1224 msgid "OAuth Refresh Token" msgstr "Token de atualização OAuth" -#: src/iac_code/commands/auth.py:1223 +#: src/iac_code/commands/auth.py:1225 msgid "OAuth Access Token Expire" msgstr "Expiração do token de acesso OAuth" -#: src/iac_code/commands/auth.py:1224 +#: src/iac_code/commands/auth.py:1226 msgid "OAuth Refresh Token Expire" msgstr "Expiração do token de atualização OAuth" -#: src/iac_code/commands/auth.py:1225 +#: src/iac_code/commands/auth.py:1227 msgid "STS Expiration" msgstr "Expiração STS" -#: src/iac_code/commands/auth.py:1382 +#: src/iac_code/commands/auth.py:1384 msgid "China" msgstr "China" -#: src/iac_code/commands/auth.py:1383 +#: src/iac_code/commands/auth.py:1385 msgid "International" msgstr "Internacional" -#: src/iac_code/commands/auth.py:1385 +#: src/iac_code/commands/auth.py:1387 msgid "Choose site type" msgstr "Escolha o tipo de site" -#: src/iac_code/commands/auth.py:1400 +#: src/iac_code/commands/auth.py:1402 #, python-brace-format msgid "Alibaba Cloud OAuth login failed: {error}" msgstr "Falha no login OAuth da Alibaba Cloud: {error}" -#: src/iac_code/commands/auth.py:1416 +#: src/iac_code/commands/auth.py:1418 msgid "Configured: Alibaba Cloud OAuth credentials saved" msgstr "Configurado: credenciais OAuth da Alibaba Cloud salvas" -#: src/iac_code/commands/auth.py:1428 +#: src/iac_code/commands/auth.py:1430 msgid "Configure Alibaba Cloud credentials" msgstr "Configurar credenciais da Alibaba Cloud" -#: src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1443 msgid "Reconfigure credential" msgstr "Reconfigurar credencial" -#: src/iac_code/commands/auth.py:1454 +#: src/iac_code/commands/auth.py:1456 msgid "Select credential type" msgstr "Selecionar tipo de credencial" -#: src/iac_code/commands/auth.py:1510 +#: src/iac_code/commands/auth.py:1512 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:1517 +#: src/iac_code/commands/auth.py:1519 msgid "Configure Alibaba Cloud region" msgstr "Configurar região da Alibaba Cloud" -#: src/iac_code/commands/auth.py:1543 +#: src/iac_code/commands/auth.py:1545 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "Configurado: região da Alibaba Cloud salva em ~/.iac-code" @@ -1151,11 +1151,11 @@ msgstr "Modelo atual: {model}" msgid "Kept model as {model}" msgstr "Modelo mantido como {model}" -#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +#: src/iac_code/commands/rename.py:16 src/iac_code/commands/rename.py:28 msgid "Rename is only available in interactive mode." msgstr "Renomear só está disponível no modo interativo." -#: src/iac_code/commands/rename.py:30 +#: src/iac_code/commands/rename.py:31 msgid "Rename cancelled" msgstr "Renomeação cancelada" @@ -1357,79 +1357,79 @@ msgstr "" "correta (atual: {base_url}). Muitos endpoints compatíveis com OpenAI " "exigem o sufixo /v1 (por exemplo, {base_url}/v1)." -#: src/iac_code/providers/registry.py:415 +#: src/iac_code/providers/registry.py:416 msgid "Alibaba Cloud Bailian" msgstr "Alibaba Cloud Bailian" -#: src/iac_code/providers/registry.py:416 +#: src/iac_code/providers/registry.py:417 msgid "Alibaba Cloud Bailian Token Plan" msgstr "Alibaba Cloud Bailian Token Plan" -#: src/iac_code/providers/registry.py:420 +#: src/iac_code/providers/registry.py:421 msgid "OpenAPI Compatible" msgstr "Compatível com OpenAPI" -#: src/iac_code/providers/registry.py:422 +#: src/iac_code/providers/registry.py:423 msgid "Kimi (China)" msgstr "Kimi (China)" -#: src/iac_code/providers/registry.py:423 +#: src/iac_code/providers/registry.py:424 msgid "Kimi (International)" msgstr "Kimi (Internacional)" -#: src/iac_code/providers/registry.py:424 +#: src/iac_code/providers/registry.py:425 msgid "MiniMax (China)" msgstr "MiniMax (China)" -#: src/iac_code/providers/registry.py:425 +#: src/iac_code/providers/registry.py:426 msgid "MiniMax (International)" msgstr "MiniMax (Internacional)" -#: src/iac_code/providers/registry.py:427 +#: src/iac_code/providers/registry.py:428 msgid "ZhiPu AI (International)" msgstr "ZhiPu AI (Internacional)" -#: src/iac_code/providers/registry.py:429 +#: src/iac_code/providers/registry.py:430 msgid "SiliconFlow (China)" msgstr "SiliconFlow (China)" -#: src/iac_code/providers/registry.py:430 +#: src/iac_code/providers/registry.py:431 msgid "SiliconFlow (International)" msgstr "SiliconFlow (Internacional)" -#: src/iac_code/providers/registry.py:431 +#: src/iac_code/providers/registry.py:432 msgid "Ollama (Local)" msgstr "Ollama (Local)" -#: src/iac_code/providers/registry.py:432 +#: src/iac_code/providers/registry.py:433 msgid "LM Studio (Local)" msgstr "LM Studio (Local)" -#: src/iac_code/providers/registry.py:435 +#: src/iac_code/providers/registry.py:436 msgid "ModelScope" msgstr "ModelScope" -#: src/iac_code/providers/registry.py:436 +#: src/iac_code/providers/registry.py:437 msgid "Alibaba Cloud CodingPlan" msgstr "Alibaba Cloud CodingPlan" -#: src/iac_code/providers/registry.py:437 +#: src/iac_code/providers/registry.py:438 msgid "Alibaba Cloud CodingPlan (International)" msgstr "Alibaba Cloud CodingPlan (Internacional)" -#: src/iac_code/providers/registry.py:438 +#: src/iac_code/providers/registry.py:439 msgid "ZhiPu AI CodingPlan" msgstr "ZhiPu AI CodingPlan" -#: src/iac_code/providers/registry.py:439 +#: src/iac_code/providers/registry.py:440 msgid "ZhiPu AI CodingPlan (International)" msgstr "ZhiPu AI CodingPlan (Internacional)" -#: src/iac_code/providers/registry.py:440 +#: src/iac_code/providers/registry.py:441 msgid "Volcengine CodingPlan" msgstr "Volcengine CodingPlan" -#: src/iac_code/providers/registry.py:441 +#: src/iac_code/providers/registry.py:442 msgid "Anthropic Compatible" msgstr "Compatível com Anthropic" @@ -1584,7 +1584,7 @@ msgstr "" "entre novamente se necessário, e execute /auth para escolher Login OAuth " "(navegador) novamente." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "A habilidade '{name}' está desativada. Execute /skills para ativá-la." @@ -2310,113 +2310,113 @@ msgstr "Não, sempre negar \"{rule}\" (esta sessão)" msgid "No, always reject this tool" msgstr "Não, sempre rejeitar esta ferramenta" -#: src/iac_code/ui/repl.py:412 +#: src/iac_code/ui/repl.py:423 msgid "Press Ctrl+C again to exit." msgstr "Pressione Ctrl+C novamente para sair." -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:448 msgid "Interrupted." msgstr "Interrompido." -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:507 msgid "Update now" msgstr "Atualizar agora" -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:509 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Executa o comando de atualização mostrado e sai quando ele for concluído " "com sucesso." -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:512 msgid "Skip" msgstr "Ignorar" -#: src/iac_code/ui/repl.py:503 +#: src/iac_code/ui/repl.py:514 msgid "Continue with the current version for this session." msgstr "Continuar com a versão atual nesta sessão." -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:517 msgid "Skip until next version" msgstr "Ignorar até a próxima versão" -#: src/iac_code/ui/repl.py:508 +#: src/iac_code/ui/repl.py:519 msgid "Hide this update until a newer version is available." msgstr "Ocultar esta atualização até que uma versão mais nova esteja disponível." -#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 +#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 msgid "Update command failed. Continuing with the current version." msgstr "O comando de atualização falhou. Continuando com a versão atual." -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:543 msgid "Update completed. Restart iac-code to continue." msgstr "Atualização concluída. Reinicie o iac-code para continuar." -#: src/iac_code/ui/repl.py:570 +#: src/iac_code/ui/repl.py:581 msgid "No image in clipboard." msgstr "Nenhuma imagem na área de transferência." -#: src/iac_code/ui/repl.py:756 +#: src/iac_code/ui/repl.py:767 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:761 +#: src/iac_code/ui/repl.py:774 msgid "Shell command support is unavailable." msgstr "O suporte a comandos shell não está disponível." -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:844 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Habilidade desconhecida: ${name}. Digite / para listar comandos e " "habilidades." -#: src/iac_code/ui/repl.py:830 +#: src/iac_code/ui/repl.py:846 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "Comando desconhecido: /{name}. Digite /help para ver os comandos." -#: src/iac_code/ui/repl.py:835 +#: src/iac_code/ui/repl.py:851 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ invoca apenas habilidades. Use /{name} em vez disso." -#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 +#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 #, python-brace-format msgid "Command error: {error}" msgstr "Erro de comando: {error}" -#: src/iac_code/ui/repl.py:864 +#: src/iac_code/ui/repl.py:880 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Comando sem tratador: {name}" -#: src/iac_code/ui/repl.py:1126 +#: src/iac_code/ui/repl.py:1155 msgid "Goodbye!" msgstr "Até logo!" -#: src/iac_code/ui/repl.py:1127 +#: src/iac_code/ui/repl.py:1156 msgid "Resume this session with:" msgstr "Para retomar esta sessão, execute:" -#: src/iac_code/ui/repl.py:1130 +#: src/iac_code/ui/repl.py:1159 msgid "Session ID" msgstr "ID da sessão" -#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 +#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sessão não encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1233 +#: src/iac_code/ui/repl.py:1262 msgid "Session name: " msgstr "Nome da sessão: " -#: src/iac_code/ui/repl.py:1239 +#: src/iac_code/ui/repl.py:1268 msgid "Session name cannot be empty." msgstr "O nome da sessão não pode estar vazio." -#: src/iac_code/ui/repl.py:1251 +#: src/iac_code/ui/repl.py:1280 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2427,23 +2427,23 @@ msgstr "" "Para retomar, execute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1257 +#: src/iac_code/ui/repl.py:1286 msgid "Multiple sessions match. Resume one by ID:" msgstr "Várias sessões correspondem. Retome uma por ID:" -#: src/iac_code/ui/repl.py:1370 +#: src/iac_code/ui/repl.py:1401 msgid "This conversation is from a different directory." msgstr "Esta conversa é de outro diretório." -#: src/iac_code/ui/repl.py:1372 +#: src/iac_code/ui/repl.py:1403 msgid "To resume, run:" msgstr "Para retomar, execute:" -#: src/iac_code/ui/repl.py:1377 +#: src/iac_code/ui/repl.py:1408 msgid "(Command copied to clipboard)" msgstr "(Comando copiado para a área de transferência)" -#: src/iac_code/ui/repl.py:1534 +#: src/iac_code/ui/repl.py:1565 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2452,12 +2452,12 @@ msgstr "" "O modelo atual {model} não suporta entrada de imagem. Use /model para " "alternar para um modelo com capacidade de visão." -#: src/iac_code/ui/repl.py:1543 +#: src/iac_code/ui/repl.py:1574 #, python-brace-format msgid "Image error: {err}" msgstr "Erro de imagem: {err}" -#: src/iac_code/ui/repl.py:1560 +#: src/iac_code/ui/repl.py:1591 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 665de49..72681df 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.3.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 22:08+0800\n" +"POT-Creation-Date: 2026-06-03 11:39+0800\n" "PO-Revision-Date: 2026-04-02 00:00+0000\n" "Last-Translator: \n" "Language: zh\n" @@ -117,7 +117,7 @@ msgstr "用法:/debug [on|off]" msgid "Memory manager is unavailable." msgstr "记忆管理器不可用。" -#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:20 +#: src/iac_code/acp/slash_registry.py:146 src/iac_code/commands/rename.py:21 msgid "Usage: /rename " msgstr "用法:/rename <名称>" @@ -125,18 +125,18 @@ msgstr "用法:/rename <名称>" msgid "Rename is only available after a session is created." msgstr "创建会话后才能重命名。" -#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:41 +#: src/iac_code/acp/slash_registry.py:163 src/iac_code/commands/rename.py:42 #, python-brace-format msgid "Session is already named {name}" msgstr "会话已命名为 {name}" -#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:42 +#: src/iac_code/acp/slash_registry.py:164 src/iac_code/commands/rename.py:43 #, python-brace-format msgid "Renamed session to {name}" msgstr "已将会话重命名为 {name}" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:795 src/iac_code/ui/repl.py:809 +#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 msgid "Permission denied." msgstr "权限被拒绝。" @@ -676,7 +676,7 @@ msgstr "管理技能" msgid "Show current session status" msgstr "显示当前会话状态" -#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1115 +#: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:1117 #: src/iac_code/ui/core/prompt_input.py:557 msgid "Navigate" msgstr "导航" @@ -684,8 +684,8 @@ msgstr "导航" #: src/iac_code/commands/auth.py:390 src/iac_code/commands/auth.py:538 #: src/iac_code/commands/auth.py:570 src/iac_code/commands/auth.py:577 #: src/iac_code/commands/auth.py:612 src/iac_code/commands/auth.py:619 -#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1115 -#: src/iac_code/commands/auth.py:1549 src/iac_code/ui/core/prompt_input.py:557 +#: src/iac_code/commands/auth.py:640 src/iac_code/commands/auth.py:1117 +#: src/iac_code/commands/auth.py:1551 src/iac_code/ui/core/prompt_input.py:557 msgid "Confirm" msgstr "确认" @@ -693,8 +693,8 @@ msgstr "确认" #: src/iac_code/commands/auth.py:538 src/iac_code/commands/auth.py:570 #: src/iac_code/commands/auth.py:577 src/iac_code/commands/auth.py:612 #: src/iac_code/commands/auth.py:619 src/iac_code/commands/auth.py:640 -#: src/iac_code/commands/auth.py:1115 src/iac_code/commands/auth.py:1441 -#: src/iac_code/commands/auth.py:1549 +#: src/iac_code/commands/auth.py:1117 src/iac_code/commands/auth.py:1443 +#: src/iac_code/commands/auth.py:1551 msgid "Back" msgstr "返回" @@ -706,9 +706,9 @@ msgstr "保留" msgid "Re-enter" msgstr "重新输入" -#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:861 -#: src/iac_code/commands/auth.py:919 src/iac_code/commands/auth.py:927 -#: src/iac_code/commands/auth.py:954 +#: src/iac_code/commands/auth.py:749 src/iac_code/commands/auth.py:863 +#: src/iac_code/commands/auth.py:921 src/iac_code/commands/auth.py:929 +#: src/iac_code/commands/auth.py:956 msgid " (current)" msgstr " (当前)" @@ -733,247 +733,247 @@ msgstr "输入自定义模型名称:" msgid "Error: console not available" msgstr "错误:控制台不可用" -#: src/iac_code/commands/auth.py:818 +#: src/iac_code/commands/auth.py:820 msgid "Configure LLM Provider" msgstr "配置 LLM 提供商" -#: src/iac_code/commands/auth.py:819 +#: src/iac_code/commands/auth.py:821 msgid "Configure IaC Cloud Service" msgstr "配置 IaC 云服务" -#: src/iac_code/commands/auth.py:821 +#: src/iac_code/commands/auth.py:823 msgid "Select configuration type" msgstr "选择配置类型" -#: src/iac_code/commands/auth.py:823 src/iac_code/commands/auth.py:979 -#: src/iac_code/commands/auth.py:995 src/iac_code/commands/auth.py:1074 -#: src/iac_code/commands/auth.py:1488 src/iac_code/commands/auth.py:1529 +#: src/iac_code/commands/auth.py:825 src/iac_code/commands/auth.py:981 +#: src/iac_code/commands/auth.py:997 src/iac_code/commands/auth.py:1076 +#: src/iac_code/commands/auth.py:1490 src/iac_code/commands/auth.py:1531 msgid "Auth cancelled" msgstr "认证已取消" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:959 -#: src/iac_code/commands/auth.py:1049 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:961 +#: src/iac_code/commands/auth.py:1051 #, python-brace-format msgid "Select provider — {group}" msgstr "选择提供商 — {group}" -#: src/iac_code/commands/auth.py:865 src/iac_code/commands/auth.py:917 -#: src/iac_code/commands/auth.py:1034 +#: src/iac_code/commands/auth.py:867 src/iac_code/commands/auth.py:919 +#: src/iac_code/commands/auth.py:1036 msgid "Third-party" msgstr "第三方" -#: src/iac_code/commands/auth.py:875 +#: src/iac_code/commands/auth.py:877 #, python-brace-format msgid "{status}: {provider}" msgstr "{status}:{provider}" -#: src/iac_code/commands/auth.py:876 src/iac_code/commands/auth.py:1027 +#: src/iac_code/commands/auth.py:878 src/iac_code/commands/auth.py:1029 msgid "Configured" msgstr "已配置" -#: src/iac_code/commands/auth.py:931 +#: src/iac_code/commands/auth.py:933 msgid "Select provider" msgstr "选择提供商" -#: src/iac_code/commands/auth.py:972 +#: src/iac_code/commands/auth.py:974 #, python-brace-format msgid "Configure {provider}" msgstr "配置 {provider}" -#: src/iac_code/commands/auth.py:988 +#: src/iac_code/commands/auth.py:990 #, python-brace-format msgid "Enter API key for {provider}" msgstr "为 {provider} 输入 API 密钥" -#: src/iac_code/commands/auth.py:1026 +#: src/iac_code/commands/auth.py:1028 #, python-brace-format msgid "{status}: {provider} / {model}" msgstr "{status}:{provider} / {model}" -#: src/iac_code/commands/auth.py:1035 src/iac_code/commands/auth.py:1056 +#: src/iac_code/commands/auth.py:1037 src/iac_code/commands/auth.py:1058 msgid "Alibaba Cloud" msgstr "阿里云" -#: src/iac_code/commands/auth.py:1036 src/iac_code/providers/registry.py:426 +#: src/iac_code/commands/auth.py:1038 src/iac_code/providers/registry.py:427 msgid "ZhiPu AI" msgstr "智谱 AI" -#: src/iac_code/commands/auth.py:1037 +#: src/iac_code/commands/auth.py:1039 msgid "Kimi" msgstr "Kimi" -#: src/iac_code/commands/auth.py:1038 +#: src/iac_code/commands/auth.py:1040 msgid "MiniMax" msgstr "MiniMax" -#: src/iac_code/commands/auth.py:1039 src/iac_code/providers/registry.py:428 +#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:429 msgid "Volcengine" msgstr "火山引擎" -#: src/iac_code/commands/auth.py:1040 +#: src/iac_code/commands/auth.py:1042 msgid "SiliconFlow" msgstr "硅基流动" -#: src/iac_code/commands/auth.py:1041 src/iac_code/providers/registry.py:419 +#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:420 msgid "DeepSeek" msgstr "DeepSeek" -#: src/iac_code/commands/auth.py:1042 src/iac_code/providers/registry.py:417 +#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:418 msgid "OpenAI" msgstr "OpenAI" -#: src/iac_code/commands/auth.py:1043 src/iac_code/providers/registry.py:418 +#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:419 msgid "Anthropic" msgstr "Anthropic" -#: src/iac_code/commands/auth.py:1044 src/iac_code/providers/registry.py:421 +#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:422 msgid "Google Gemini" msgstr "Google Gemini" -#: src/iac_code/commands/auth.py:1045 src/iac_code/providers/registry.py:434 +#: src/iac_code/commands/auth.py:1047 src/iac_code/providers/registry.py:435 msgid "Azure OpenAI" msgstr "Azure OpenAI" -#: src/iac_code/commands/auth.py:1046 src/iac_code/providers/registry.py:433 +#: src/iac_code/commands/auth.py:1048 src/iac_code/providers/registry.py:434 msgid "OpenRouter" msgstr "OpenRouter" -#: src/iac_code/commands/auth.py:1047 +#: src/iac_code/commands/auth.py:1049 msgid "Local" msgstr "本地模型" -#: src/iac_code/commands/auth.py:1048 +#: src/iac_code/commands/auth.py:1050 msgid "Compatible" msgstr "兼容模式" -#: src/iac_code/commands/auth.py:1065 +#: src/iac_code/commands/auth.py:1067 msgid "Select Cloud Provider" msgstr "选择云服务商" -#: src/iac_code/commands/auth.py:1081 +#: src/iac_code/commands/auth.py:1083 msgid "Credential" msgstr "凭证" -#: src/iac_code/commands/auth.py:1082 src/iac_code/commands/auth.py:1197 -#: src/iac_code/commands/auth.py:1525 src/iac_code/commands/status.py:36 +#: src/iac_code/commands/auth.py:1084 src/iac_code/commands/auth.py:1199 +#: src/iac_code/commands/auth.py:1527 src/iac_code/commands/status.py:36 #: src/iac_code/ui/renderer.py:455 msgid "Region" msgstr "地域" -#: src/iac_code/commands/auth.py:1084 +#: src/iac_code/commands/auth.py:1086 msgid "Configure Alibaba Cloud" msgstr "配置阿里云" -#: src/iac_code/commands/auth.py:1186 +#: src/iac_code/commands/auth.py:1188 msgid "Current configuration" msgstr "当前配置" -#: src/iac_code/commands/auth.py:1188 +#: src/iac_code/commands/auth.py:1190 msgid "Mode" msgstr "模式" -#: src/iac_code/commands/auth.py:1194 +#: src/iac_code/commands/auth.py:1196 msgid "(not set)" msgstr "(未设置)" -#: src/iac_code/commands/auth.py:1203 +#: src/iac_code/commands/auth.py:1205 msgid "AccessKey" msgstr "AccessKey" -#: src/iac_code/commands/auth.py:1205 src/iac_code/commands/auth.py:1217 +#: src/iac_code/commands/auth.py:1207 src/iac_code/commands/auth.py:1219 msgid "STS Token" msgstr "STS 令牌" -#: src/iac_code/commands/auth.py:1207 +#: src/iac_code/commands/auth.py:1209 msgid "RAM Role" msgstr "RAM 角色" -#: src/iac_code/commands/auth.py:1209 +#: src/iac_code/commands/auth.py:1211 msgid "OAuth Login (Browser)" msgstr "OAuth 登录(浏览器)" -#: src/iac_code/commands/auth.py:1215 +#: src/iac_code/commands/auth.py:1217 msgid "AccessKey ID" msgstr "AccessKey ID" -#: src/iac_code/commands/auth.py:1216 +#: src/iac_code/commands/auth.py:1218 msgid "AccessKey Secret" msgstr "AccessKey 密钥" -#: src/iac_code/commands/auth.py:1218 +#: src/iac_code/commands/auth.py:1220 msgid "RAM Role ARN" msgstr "RAM 角色 ARN" -#: src/iac_code/commands/auth.py:1219 +#: src/iac_code/commands/auth.py:1221 msgid "Session Name" msgstr "会话名称" -#: src/iac_code/commands/auth.py:1220 +#: src/iac_code/commands/auth.py:1222 msgid "OAuth Site Type" msgstr "OAuth 站点类型" -#: src/iac_code/commands/auth.py:1221 +#: src/iac_code/commands/auth.py:1223 msgid "OAuth Access Token" msgstr "OAuth 访问令牌" -#: src/iac_code/commands/auth.py:1222 +#: src/iac_code/commands/auth.py:1224 msgid "OAuth Refresh Token" msgstr "OAuth 刷新令牌" -#: src/iac_code/commands/auth.py:1223 +#: src/iac_code/commands/auth.py:1225 msgid "OAuth Access Token Expire" msgstr "OAuth 访问令牌过期时间" -#: src/iac_code/commands/auth.py:1224 +#: src/iac_code/commands/auth.py:1226 msgid "OAuth Refresh Token Expire" msgstr "OAuth 刷新令牌过期时间" -#: src/iac_code/commands/auth.py:1225 +#: src/iac_code/commands/auth.py:1227 msgid "STS Expiration" msgstr "STS 过期时间" -#: src/iac_code/commands/auth.py:1382 +#: src/iac_code/commands/auth.py:1384 msgid "China" msgstr "中国" -#: src/iac_code/commands/auth.py:1383 +#: src/iac_code/commands/auth.py:1385 msgid "International" msgstr "国际" -#: src/iac_code/commands/auth.py:1385 +#: src/iac_code/commands/auth.py:1387 msgid "Choose site type" msgstr "选择站点类型" -#: src/iac_code/commands/auth.py:1400 +#: src/iac_code/commands/auth.py:1402 #, python-brace-format msgid "Alibaba Cloud OAuth login failed: {error}" msgstr "阿里云 OAuth 登录失败:{error}" -#: src/iac_code/commands/auth.py:1416 +#: src/iac_code/commands/auth.py:1418 msgid "Configured: Alibaba Cloud OAuth credentials saved" msgstr "已配置:阿里云 OAuth 凭证已保存" -#: src/iac_code/commands/auth.py:1428 +#: src/iac_code/commands/auth.py:1430 msgid "Configure Alibaba Cloud credentials" msgstr "配置阿里云凭证" -#: src/iac_code/commands/auth.py:1441 +#: src/iac_code/commands/auth.py:1443 msgid "Reconfigure credential" msgstr "重新配置凭证" -#: src/iac_code/commands/auth.py:1454 +#: src/iac_code/commands/auth.py:1456 msgid "Select credential type" msgstr "选择凭证类型" -#: src/iac_code/commands/auth.py:1510 +#: src/iac_code/commands/auth.py:1512 msgid "Configured: Alibaba Cloud credentials saved to ~/.iac-code" msgstr "已配置:阿里云凭证已保存到 ~/.iac-code" -#: src/iac_code/commands/auth.py:1517 +#: src/iac_code/commands/auth.py:1519 msgid "Configure Alibaba Cloud region" msgstr "配置阿里云地域" -#: src/iac_code/commands/auth.py:1543 +#: src/iac_code/commands/auth.py:1545 msgid "Configured: Alibaba Cloud region saved to ~/.iac-code" msgstr "已配置:阿里云地域已保存到 ~/.iac-code" @@ -1121,11 +1121,11 @@ msgstr "当前模型:{model}" msgid "Kept model as {model}" msgstr "保持模型为 {model}" -#: src/iac_code/commands/rename.py:15 src/iac_code/commands/rename.py:27 +#: src/iac_code/commands/rename.py:16 src/iac_code/commands/rename.py:28 msgid "Rename is only available in interactive mode." msgstr "重命名仅在交互模式下可用。" -#: src/iac_code/commands/rename.py:30 +#: src/iac_code/commands/rename.py:31 msgid "Rename cancelled" msgstr "已取消重命名" @@ -1319,79 +1319,79 @@ msgstr "" "API 返回了无效响应。请检查您的 API Base URL 是否正确(当前:{base_url})。许多 OpenAI 兼容端点需要 /v1 " "后缀(如 {base_url}/v1)。" -#: src/iac_code/providers/registry.py:415 +#: src/iac_code/providers/registry.py:416 msgid "Alibaba Cloud Bailian" msgstr "阿里云百炼" -#: src/iac_code/providers/registry.py:416 +#: src/iac_code/providers/registry.py:417 msgid "Alibaba Cloud Bailian Token Plan" msgstr "阿里云百炼 Token Plan" -#: src/iac_code/providers/registry.py:420 +#: src/iac_code/providers/registry.py:421 msgid "OpenAPI Compatible" msgstr "OpenAPI 兼容" -#: src/iac_code/providers/registry.py:422 +#: src/iac_code/providers/registry.py:423 msgid "Kimi (China)" msgstr "Kimi(中国版)" -#: src/iac_code/providers/registry.py:423 +#: src/iac_code/providers/registry.py:424 msgid "Kimi (International)" msgstr "Kimi(国际版)" -#: src/iac_code/providers/registry.py:424 +#: src/iac_code/providers/registry.py:425 msgid "MiniMax (China)" msgstr "MiniMax(中国版)" -#: src/iac_code/providers/registry.py:425 +#: src/iac_code/providers/registry.py:426 msgid "MiniMax (International)" msgstr "MiniMax(国际版)" -#: src/iac_code/providers/registry.py:427 +#: src/iac_code/providers/registry.py:428 msgid "ZhiPu AI (International)" msgstr "智谱 AI(国际版)" -#: src/iac_code/providers/registry.py:429 +#: src/iac_code/providers/registry.py:430 msgid "SiliconFlow (China)" msgstr "硅基流动(中国版)" -#: src/iac_code/providers/registry.py:430 +#: src/iac_code/providers/registry.py:431 msgid "SiliconFlow (International)" msgstr "硅基流动(国际版)" -#: src/iac_code/providers/registry.py:431 +#: src/iac_code/providers/registry.py:432 msgid "Ollama (Local)" msgstr "Ollama(本地)" -#: src/iac_code/providers/registry.py:432 +#: src/iac_code/providers/registry.py:433 msgid "LM Studio (Local)" msgstr "LM Studio(本地)" -#: src/iac_code/providers/registry.py:435 +#: src/iac_code/providers/registry.py:436 msgid "ModelScope" msgstr "魔搭" -#: src/iac_code/providers/registry.py:436 +#: src/iac_code/providers/registry.py:437 msgid "Alibaba Cloud CodingPlan" msgstr "阿里云编程计划" -#: src/iac_code/providers/registry.py:437 +#: src/iac_code/providers/registry.py:438 msgid "Alibaba Cloud CodingPlan (International)" msgstr "阿里云编程计划(国际版)" -#: src/iac_code/providers/registry.py:438 +#: src/iac_code/providers/registry.py:439 msgid "ZhiPu AI CodingPlan" msgstr "智谱 AI 编程计划" -#: src/iac_code/providers/registry.py:439 +#: src/iac_code/providers/registry.py:440 msgid "ZhiPu AI CodingPlan (International)" msgstr "智谱 AI 编程计划(国际版)" -#: src/iac_code/providers/registry.py:440 +#: src/iac_code/providers/registry.py:441 msgid "Volcengine CodingPlan" msgstr "火山引擎编程计划" -#: src/iac_code/providers/registry.py:441 +#: src/iac_code/providers/registry.py:442 msgid "Anthropic Compatible" msgstr "Anthropic 兼容" @@ -1533,7 +1533,7 @@ msgstr "" " RAM 角色。不支持用户组。然后关闭旧的授权页面,必要时退出 Alibaba Cloud 并重新登录,再运行 /auth 重新选择 OAuth " "登录(浏览器)。" -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:825 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "技能“{name}”已禁用。运行 /skills 以启用它。" @@ -2243,109 +2243,109 @@ msgstr "否,始终拒绝 \"{rule}\"(本次会话)" msgid "No, always reject this tool" msgstr "否,始终拒绝此工具" -#: src/iac_code/ui/repl.py:412 +#: src/iac_code/ui/repl.py:423 msgid "Press Ctrl+C again to exit." msgstr "再次按 Ctrl+C 退出。" -#: src/iac_code/ui/repl.py:437 +#: src/iac_code/ui/repl.py:448 msgid "Interrupted." msgstr "已中断。" -#: src/iac_code/ui/repl.py:496 +#: src/iac_code/ui/repl.py:507 msgid "Update now" msgstr "立即更新" -#: src/iac_code/ui/repl.py:498 +#: src/iac_code/ui/repl.py:509 msgid "Run the shown update command and exit when it succeeds." msgstr "运行显示的更新命令,成功后退出。" -#: src/iac_code/ui/repl.py:501 +#: src/iac_code/ui/repl.py:512 msgid "Skip" msgstr "跳过" -#: src/iac_code/ui/repl.py:503 +#: src/iac_code/ui/repl.py:514 msgid "Continue with the current version for this session." msgstr "本次会话继续使用当前版本。" -#: src/iac_code/ui/repl.py:506 +#: src/iac_code/ui/repl.py:517 msgid "Skip until next version" msgstr "跳过直到下一个版本" -#: src/iac_code/ui/repl.py:508 +#: src/iac_code/ui/repl.py:519 msgid "Hide this update until a newer version is available." msgstr "隐藏此更新,直到有更新的版本可用。" -#: src/iac_code/ui/repl.py:527 src/iac_code/ui/repl.py:539 +#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 msgid "Update command failed. Continuing with the current version." msgstr "更新命令失败。将继续使用当前版本。" -#: src/iac_code/ui/repl.py:532 +#: src/iac_code/ui/repl.py:543 msgid "Update completed. Restart iac-code to continue." msgstr "更新已完成。请重启 iac-code 以继续。" -#: src/iac_code/ui/repl.py:570 +#: src/iac_code/ui/repl.py:581 msgid "No image in clipboard." msgstr "剪贴板中没有图像。" -#: src/iac_code/ui/repl.py:756 +#: src/iac_code/ui/repl.py:767 msgid "Usage: !" msgstr "用法:!" -#: src/iac_code/ui/repl.py:761 +#: src/iac_code/ui/repl.py:774 msgid "Shell command support is unavailable." msgstr "Shell 命令支持不可用。" -#: src/iac_code/ui/repl.py:828 +#: src/iac_code/ui/repl.py:844 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "未知技能:${name}。输入 / 可列出命令和技能。" -#: src/iac_code/ui/repl.py:830 +#: src/iac_code/ui/repl.py:846 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "未知命令:/{name}。输入 /help 查看可用命令。" -#: src/iac_code/ui/repl.py:835 +#: src/iac_code/ui/repl.py:851 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ 只能调用技能。请改用 /{name}。" -#: src/iac_code/ui/repl.py:857 src/iac_code/ui/repl.py:902 +#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 #, python-brace-format msgid "Command error: {error}" msgstr "命令错误:{error}" -#: src/iac_code/ui/repl.py:864 +#: src/iac_code/ui/repl.py:880 #, python-brace-format msgid "Command has no handler: {name}" msgstr "命令没有处理器:{name}" -#: src/iac_code/ui/repl.py:1126 +#: src/iac_code/ui/repl.py:1155 msgid "Goodbye!" msgstr "再见!" -#: src/iac_code/ui/repl.py:1127 +#: src/iac_code/ui/repl.py:1156 msgid "Resume this session with:" msgstr "恢复此会话请运行:" -#: src/iac_code/ui/repl.py:1130 +#: src/iac_code/ui/repl.py:1159 msgid "Session ID" msgstr "会话 ID" -#: src/iac_code/ui/repl.py:1180 src/iac_code/ui/repl.py:1184 +#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 #, python-brace-format msgid "Session not found: {session_id}" msgstr "会话不存在:{session_id}" -#: src/iac_code/ui/repl.py:1233 +#: src/iac_code/ui/repl.py:1262 msgid "Session name: " msgstr "会话名称:" -#: src/iac_code/ui/repl.py:1239 +#: src/iac_code/ui/repl.py:1268 msgid "Session name cannot be empty." msgstr "会话名称不能为空。" -#: src/iac_code/ui/repl.py:1251 +#: src/iac_code/ui/repl.py:1280 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2356,35 +2356,35 @@ msgstr "" "请运行以下命令恢复:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1257 +#: src/iac_code/ui/repl.py:1286 msgid "Multiple sessions match. Resume one by ID:" msgstr "匹配到多个会话。请通过 ID 恢复其中一个:" -#: src/iac_code/ui/repl.py:1370 +#: src/iac_code/ui/repl.py:1401 msgid "This conversation is from a different directory." msgstr "该会话来自另一个目录。" -#: src/iac_code/ui/repl.py:1372 +#: src/iac_code/ui/repl.py:1403 msgid "To resume, run:" msgstr "请运行以下命令恢复:" -#: src/iac_code/ui/repl.py:1377 +#: src/iac_code/ui/repl.py:1408 msgid "(Command copied to clipboard)" msgstr "(命令已复制到剪贴板)" -#: src/iac_code/ui/repl.py:1534 +#: src/iac_code/ui/repl.py:1565 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " "to a vision-capable model." msgstr "当前模型 {model} 不支持图像输入。请使用 /model 切换到支持视觉的模型。" -#: src/iac_code/ui/repl.py:1543 +#: src/iac_code/ui/repl.py:1574 #, python-brace-format msgid "Image error: {err}" msgstr "图像错误:{err}" -#: src/iac_code/ui/repl.py:1560 +#: src/iac_code/ui/repl.py:1591 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." diff --git a/src/iac_code/providers/registry.py b/src/iac_code/providers/registry.py index 04289a3..b818982 100644 --- a/src/iac_code/providers/registry.py +++ b/src/iac_code/providers/registry.py @@ -46,6 +46,7 @@ def model_ids(self) -> list[str]: base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", models=[ ModelEntry("qwen3.7-max", is_default=True), + ModelEntry("qwen3.7-plus", support_multimodal=True), ModelEntry("qwen3.6-plus", support_multimodal=True), ModelEntry("qwen3.6-max-preview"), ModelEntry("qwen3-max"), diff --git a/src/iac_code/providers/thinking.py b/src/iac_code/providers/thinking.py index 6c0eb71..5949d2b 100644 --- a/src/iac_code/providers/thinking.py +++ b/src/iac_code/providers/thinking.py @@ -131,6 +131,7 @@ def effort_range(self) -> tuple[EffortLevel, EffortLevel] | None: }, "dashscope": { "qwen3.7-max": ThinkingSpec(ThinkingFamily.DASHSCOPE), + "qwen3.7-plus": ThinkingSpec(ThinkingFamily.DASHSCOPE), "qwen3.6-max-preview": ThinkingSpec(ThinkingFamily.DASHSCOPE), "qwen3.6-plus": ThinkingSpec(ThinkingFamily.DASHSCOPE), "qwen3.5-plus": ThinkingSpec(ThinkingFamily.DASHSCOPE), From 6eb2949c1927cf433571c1dc51054ddc74e06c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 12:26:56 +0800 Subject: [PATCH 08/11] fix: improve windows resume compatibility --- src/iac_code/acp/server.py | 8 +++--- src/iac_code/ui/repl.py | 19 ++++++-------- src/iac_code/utils/project_paths.py | 39 +++++++++++++++++++++++++++++ tests/acp/test_sessions.py | 17 +++++++++++++ tests/ui/test_repl_integration.py | 23 +++++++++++++++++ tests/utils/test_project_paths.py | 19 ++++++++++++++ 6 files changed, 109 insertions(+), 16 deletions(-) diff --git a/src/iac_code/acp/server.py b/src/iac_code/acp/server.py index 7bebdf1..6ccf006 100644 --- a/src/iac_code/acp/server.py +++ b/src/iac_code/acp/server.py @@ -3,7 +3,6 @@ import asyncio import contextlib import logging -import shlex import time import uuid from typing import Any @@ -24,6 +23,7 @@ from iac_code.services.session_index import SessionEntry, SessionIndex from iac_code.services.session_resolver import ResolutionStatus, resolve_session_argument from iac_code.services.session_storage import SessionStorage +from iac_code.utils.project_paths import format_resume_command, same_project_path SESSION_IDLE_TIMEOUT = 3600 # 1 hour CLEANUP_INTERVAL = 300 # 5 minutes @@ -429,7 +429,7 @@ async def resume_session( raise _invalid_params(_("Session not found"), {"session_id": session_id}) resolved_session_id = entry.session_id - if entry.cwd and entry.cwd != cwd: + if entry.cwd and not same_project_path(entry.cwd, cwd): hint = _resume_command(entry.cwd, resolved_session_id) message = _("Session belongs to another project. Run: {hint}").format(hint=hint) raise _invalid_params( @@ -690,7 +690,7 @@ def _invalid_params(message: str, data: dict[str, Any] | None = None) -> acp.Req def _resume_command(cwd: str, session_id: str) -> str: - return "cd {cwd} && iac-code --resume {session_id}".format(cwd=shlex.quote(cwd), session_id=session_id) + return format_resume_command(cwd, session_id) def _active_session_cwd(session: ACPSession) -> str | None: @@ -702,7 +702,7 @@ def _active_session_project_error( cwd: str, session_id: str, resolved_session_id: str, session: ACPSession ) -> acp.RequestError | None: active_cwd = _active_session_cwd(session) - if not active_cwd or active_cwd == cwd: + if not active_cwd or same_project_path(active_cwd, cwd): return None hint = _resume_command(active_cwd, resolved_session_id) message = _("Session belongs to another project. Run: {hint}").format(hint=hint) diff --git a/src/iac_code/ui/repl.py b/src/iac_code/ui/repl.py index 638cf0a..08fd3d7 100644 --- a/src/iac_code/ui/repl.py +++ b/src/iac_code/ui/repl.py @@ -67,6 +67,7 @@ from iac_code.utils.background_housekeeping import start_background_housekeeping from iac_code.utils.image.clipboard import ClipboardImage, get_image_from_clipboard, try_read_image_from_path from iac_code.utils.image.format_detect import IMAGE_EXTENSION_REGEX +from iac_code.utils.project_paths import format_resume_command, same_project_path termios: ModuleType | None try: @@ -1200,7 +1201,7 @@ def _resolve_session_id(self, resume: str | bool | None) -> str: if latest is None: return str(uuid.uuid4()) cwd, sid = latest - if cwd and cwd != self._original_cwd: + if cwd and not same_project_path(cwd, self._original_cwd): raise ValueError(self._cross_project_message(cwd, sid)) return sid if isinstance(resume, str) and resume: @@ -1211,7 +1212,7 @@ def _resolve_session_id(self, resume: str | bool | None) -> str: raise ValueError(self._ambiguous_resume_message(resolution.candidates)) if resolution.entry is None: raise ValueError(_("Session not found: {session_id}").format(session_id=resume)) - if resolution.entry.cwd and resolution.entry.cwd != self._original_cwd: + if resolution.entry.cwd and not same_project_path(resolution.entry.cwd, self._original_cwd): raise ValueError(self._cross_project_message(resolution.entry.cwd, resolution.entry.session_id)) return resolution.entry.session_id return str(uuid.uuid4()) @@ -1274,18 +1275,14 @@ async def prompt_for_session_name(self) -> str | None: @staticmethod def _cross_project_message(cwd: str, session_id: str) -> str: - import shlex - - cmd = f"cd {shlex.quote(cwd)} && iac-code --resume {session_id}" + cmd = format_resume_command(cwd, session_id) return _("This session belongs to a different directory.\nTo resume, run:\n {cmd}").format(cmd=cmd) @staticmethod def _ambiguous_resume_message(entries) -> str: - import shlex - lines = [_("Multiple sessions match. Resume one by ID:"), ""] for entry in entries: - cmd = f"cd {shlex.quote(entry.cwd)} && iac-code --resume {entry.session_id}" + cmd = format_resume_command(entry.cwd, entry.session_id) lines.append(f" {cmd}") return "\n".join(lines) @@ -1387,15 +1384,13 @@ def swap_session(self, new_session_id: str) -> None: async def swap_or_announce_session(self, entry) -> None: """Hot-swap if same project; otherwise print the resume command.""" - if entry.cwd and entry.cwd == self._original_cwd: + if entry.cwd and same_project_path(entry.cwd, self._original_cwd): self.swap_session(entry.session_id) return await self._announce_cross_project(entry) async def _announce_cross_project(self, entry) -> None: - import shlex - - cmd = f"cd {shlex.quote(entry.cwd)} && iac-code --resume {entry.session_id}" + cmd = format_resume_command(entry.cwd, entry.session_id) msg_lines = [ "", _("This conversation is from a different directory."), diff --git a/src/iac_code/utils/project_paths.py b/src/iac_code/utils/project_paths.py index 9d767b8..83d806e 100644 --- a/src/iac_code/utils/project_paths.py +++ b/src/iac_code/utils/project_paths.py @@ -9,8 +9,11 @@ from __future__ import annotations +import ntpath import os import re +import shlex +import sys from hashlib import blake2b from pathlib import Path @@ -18,6 +21,7 @@ MAX_SANITIZED_LENGTH = 200 _NON_ALNUM = re.compile(r"[^a-zA-Z0-9]") +_WINDOWS_DRIVE_PATH = re.compile(r"^[a-zA-Z]:[\\/]") def sanitize_path(name: str) -> str: @@ -53,6 +57,41 @@ def is_conversation_session_file(path: Path) -> bool: return path.name.endswith(".jsonl") and not path.name.endswith(".usage.jsonl") +def same_project_path(left: str, right: str) -> bool: + """Return whether two cwd strings identify the same project directory.""" + return _canonical_project_path(left) == _canonical_project_path(right) + + +def format_resume_command(cwd: str, session_id: str, *, platform: str | None = None) -> str: + """Build a copy-paste resume command for the current platform.""" + if (platform or sys.platform).startswith("win"): + return 'cd /d "{}" && iac-code --resume {}'.format(_escape_cmd_double_quotes(cwd), session_id) + return "cd {cwd} && iac-code --resume {session_id}".format( + cwd=shlex.quote(cwd), + session_id=shlex.quote(session_id), + ) + + +def _canonical_project_path(value: str) -> str: + expanded = os.path.expanduser(value) + if _looks_like_windows_path(expanded): + return ntpath.normcase(ntpath.normpath(expanded)) + try: + path = Path(expanded).resolve(strict=False) + except (OSError, RuntimeError): + path = Path(os.path.abspath(expanded)) + normalized = os.path.normpath(str(path)) + return os.path.normcase(normalized) + + +def _looks_like_windows_path(value: str) -> bool: + return bool(_WINDOWS_DRIVE_PATH.match(value)) or value.startswith(("\\\\", "//")) + + +def _escape_cmd_double_quotes(value: str) -> str: + return value.replace('"', '\\"') + + def _resolve_git_dir(worktree_root: str) -> str | None: """Given a worktree root, return the absolute path of its git dir. diff --git a/tests/acp/test_sessions.py b/tests/acp/test_sessions.py index 19ce1cc..fc5ad07 100644 --- a/tests/acp/test_sessions.py +++ b/tests/acp/test_sessions.py @@ -483,6 +483,23 @@ async def test_resume_active_session_returns_immediately(monkeypatch: pytest.Mon assert sid in server.sessions +@pytest.mark.asyncio +async def test_resume_active_session_accepts_windows_equivalent_cwd(monkeypatch: pytest.MonkeyPatch) -> None: + """Windows path case and separator differences should not trip project ownership checks.""" + _patch_resume_server(monkeypatch) + conn = _RecordingFakeConn() + server = ACPServer() + server.on_connect(conn) + + resp = await server.new_session(cwd=r"C:\Users\Me\Repo") + sid = resp.session_id + + result = await server.resume_session(cwd="c:/Users/Me/Repo", session_id=sid) + + assert isinstance(result, acp.schema.ResumeSessionResponse) + assert sid in server.sessions + + @pytest.mark.asyncio async def test_resume_active_session_from_other_cwd_raises_hint(monkeypatch: pytest.MonkeyPatch) -> None: """An in-memory active session follows the same project boundary as persisted sessions.""" diff --git a/tests/ui/test_repl_integration.py b/tests/ui/test_repl_integration.py index 1e04787..fd83901 100644 --- a/tests/ui/test_repl_integration.py +++ b/tests/ui/test_repl_integration.py @@ -390,6 +390,18 @@ def test_resolve_session_id_continue_returns_latest_current_project_session(): repl._session_storage.get_latest_session_anywhere.assert_called_once_with() +def test_resolve_session_id_continue_accepts_windows_equivalent_cwd(): + from iac_code.ui.repl import InlineREPL + + repl = InlineREPL.__new__(InlineREPL) + repl._original_cwd = r"C:\Users\Me\Repo" + repl._session_storage = SimpleNamespace( + get_latest_session_anywhere=Mock(return_value=("c:/Users/Me/Repo", "latest-id")) + ) + + assert repl._resolve_session_id(True) == "latest-id" + + def test_resolve_session_id_continue_cross_project_raises_with_hint(): from iac_code.ui.repl import InlineREPL @@ -403,6 +415,17 @@ def test_resolve_session_id_continue_cross_project_raises_with_hint(): repl._resolve_session_id(True) +def test_cross_project_message_uses_windows_resume_command(monkeypatch): + import iac_code.utils.project_paths as project_paths + from iac_code.ui.repl import InlineREPL + + monkeypatch.setattr(project_paths.sys, "platform", "win32") + + message = InlineREPL._cross_project_message(r"C:\Users\Me\iac repo & unsafe", "abc123") + + assert r'cd /d "C:\Users\Me\iac repo & unsafe" && iac-code --resume abc123' in message + + @patch("iac_code.ui.repl.ProviderManager") @patch("iac_code.ui.repl.SessionStorage") @patch("iac_code.ui.repl.MemoryManager") diff --git a/tests/utils/test_project_paths.py b/tests/utils/test_project_paths.py index ebb2345..b6a1169 100644 --- a/tests/utils/test_project_paths.py +++ b/tests/utils/test_project_paths.py @@ -8,7 +8,9 @@ from iac_code.utils.project_paths import ( MAX_SANITIZED_LENGTH, find_git_worktree_root, + format_resume_command, get_git_branch, + same_project_path, sanitize_path, ) @@ -42,6 +44,23 @@ def test_empty_string(self): assert sanitize_path("") == "" +class TestProjectPathComparison: + def test_windows_drive_case_and_separators_match(self): + assert same_project_path(r"C:\Users\Me\Repo", "c:/Users/Me/Repo") + + +class TestFormatResumeCommand: + def test_windows_command_quotes_for_cmd_exe(self): + command = format_resume_command(r"C:\Users\Me\iac repo & unsafe", "abc123", platform="win32") + + assert command == r'cd /d "C:\Users\Me\iac repo & unsafe" && iac-code --resume abc123' + + def test_posix_command_keeps_shell_quoting(self): + command = format_resume_command("/project a;unsafe", "abc123", platform="linux") + + assert command == "cd '/project a;unsafe' && iac-code --resume abc123" + + class TestGetGitBranch: """Regression: ``get_git_branch`` must not spawn ``git``. From 685eccb54d2cd49019d065c09a15a91222bb1fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 12:43:52 +0800 Subject: [PATCH 09/11] docs: localize website updates --- src/iac_code/__init__.py | 2 +- .../i18n/locales/de/LC_MESSAGES/messages.po | 72 +++++++++--------- .../i18n/locales/es/LC_MESSAGES/messages.po | 72 +++++++++--------- .../i18n/locales/fr/LC_MESSAGES/messages.po | 72 +++++++++--------- .../i18n/locales/ja/LC_MESSAGES/messages.po | 72 +++++++++--------- .../i18n/locales/pt/LC_MESSAGES/messages.po | 72 +++++++++--------- .../i18n/locales/zh/LC_MESSAGES/messages.po | 72 +++++++++--------- .../current/cli/command-line-options.md | 2 +- .../current/cli/commands.md | 6 +- .../current/cli/interactive-mode.md | 6 ++ .../current/cli/sessions.md | 73 ++++++++++++------- .../current/cli/skills.md | 9 +++ .../alibaba-cloud-credentials.md | 16 ++++ .../current/cli/command-line-options.md | 2 +- .../current/cli/commands.md | 6 +- .../current/cli/interactive-mode.md | 6 ++ .../current/cli/sessions.md | 73 ++++++++++++------- .../current/cli/skills.md | 9 +++ .../alibaba-cloud-credentials.md | 16 ++++ .../current/cli/command-line-options.md | 2 +- .../current/cli/commands.md | 6 +- .../current/cli/interactive-mode.md | 6 ++ .../current/cli/sessions.md | 69 +++++++++++------- .../current/cli/skills.md | 9 +++ .../alibaba-cloud-credentials.md | 16 ++++ .../current/cli/command-line-options.md | 2 +- .../current/cli/commands.md | 6 +- .../current/cli/interactive-mode.md | 6 ++ .../current/cli/sessions.md | 73 ++++++++++++------- .../current/cli/skills.md | 9 +++ .../alibaba-cloud-credentials.md | 16 ++++ .../current/cli/command-line-options.md | 2 +- .../current/cli/commands.md | 6 +- .../current/cli/interactive-mode.md | 6 ++ .../current/cli/sessions.md | 73 ++++++++++++------- .../current/cli/skills.md | 9 +++ .../alibaba-cloud-credentials.md | 16 ++++ .../current/cli/command-line-options.md | 2 +- .../current/cli/commands.md | 6 +- .../current/cli/interactive-mode.md | 6 ++ .../current/cli/sessions.md | 29 ++++++-- .../current/cli/skills.md | 9 +++ .../alibaba-cloud-credentials.md | 16 ++++ 43 files changed, 685 insertions(+), 373 deletions(-) diff --git a/src/iac_code/__init__.py b/src/iac_code/__init__.py index d78f9bf..d9b9b21 100644 --- a/src/iac_code/__init__.py +++ b/src/iac_code/__init__.py @@ -1,2 +1,2 @@ -__version__ = "0.3.1" +__version__ = "0.4.0" __release_date__ = "" 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 b5f5b50..9efef64 100644 --- a/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.1\n" +"Project-Id-Version: iac-code 0.4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-03 11:39+0800\n" +"POT-Creation-Date: 2026-06-03 13:32+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: de\n" @@ -146,7 +146,7 @@ msgid "Renamed session to {name}" msgstr "Sitzung in {name} umbenannt" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:813 src/iac_code/ui/repl.py:827 msgid "Permission denied." msgstr "Zugriff verweigert." @@ -1597,7 +1597,7 @@ msgstr "" "wieder an, und führen Sie /auth aus, um erneut OAuth-Anmeldung (Browser) " "zu wählen." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:842 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "" @@ -2325,115 +2325,115 @@ msgstr "Nein, immer \"{rule}\" ablehnen (diese Sitzung)" msgid "No, always reject this tool" msgstr "Nein, dieses Tool immer ablehnen" -#: src/iac_code/ui/repl.py:423 +#: src/iac_code/ui/repl.py:424 msgid "Press Ctrl+C again to exit." msgstr "Drücken Sie erneut Ctrl+C zum Beenden." -#: src/iac_code/ui/repl.py:448 +#: src/iac_code/ui/repl.py:449 msgid "Interrupted." msgstr "Unterbrochen." -#: src/iac_code/ui/repl.py:507 +#: src/iac_code/ui/repl.py:508 msgid "Update now" msgstr "Jetzt aktualisieren" -#: src/iac_code/ui/repl.py:509 +#: src/iac_code/ui/repl.py:510 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Führt den angezeigten Aktualisierungsbefehl aus und beendet das Programm " "bei Erfolg." -#: src/iac_code/ui/repl.py:512 +#: src/iac_code/ui/repl.py:513 msgid "Skip" msgstr "Überspringen" -#: src/iac_code/ui/repl.py:514 +#: src/iac_code/ui/repl.py:515 msgid "Continue with the current version for this session." msgstr "Für diese Sitzung mit der aktuellen Version fortfahren." -#: src/iac_code/ui/repl.py:517 +#: src/iac_code/ui/repl.py:518 msgid "Skip until next version" msgstr "Bis zur nächsten Version überspringen" -#: src/iac_code/ui/repl.py:519 +#: src/iac_code/ui/repl.py:520 msgid "Hide this update until a newer version is available." msgstr "Dieses Update ausblenden, bis eine neuere Version verfügbar ist." -#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 +#: src/iac_code/ui/repl.py:539 src/iac_code/ui/repl.py:551 msgid "Update command failed. Continuing with the current version." msgstr "" "Der Aktualisierungsbefehl ist fehlgeschlagen. Es wird mit der aktuellen " "Version fortgefahren." -#: src/iac_code/ui/repl.py:543 +#: src/iac_code/ui/repl.py:544 msgid "Update completed. Restart iac-code to continue." msgstr "Update abgeschlossen. Starten Sie iac-code neu, um fortzufahren." -#: src/iac_code/ui/repl.py:581 +#: src/iac_code/ui/repl.py:582 msgid "No image in clipboard." msgstr "Kein Bild in der Zwischenablage." -#: src/iac_code/ui/repl.py:767 +#: src/iac_code/ui/repl.py:768 msgid "Usage: !" msgstr "Verwendung: !" -#: src/iac_code/ui/repl.py:774 +#: src/iac_code/ui/repl.py:775 msgid "Shell command support is unavailable." msgstr "Shell-Befehlsunterstützung ist nicht verfügbar." -#: src/iac_code/ui/repl.py:844 +#: src/iac_code/ui/repl.py:845 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "Unbekannter Skill: ${name}. Tippe /, um Befehle und Skills aufzulisten." -#: src/iac_code/ui/repl.py:846 +#: src/iac_code/ui/repl.py:847 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available commands.Unbekannter " "Befehl: /{name}. Geben Sie /help für verfügbare Befehle ein." -#: src/iac_code/ui/repl.py:851 +#: src/iac_code/ui/repl.py:852 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ ruft nur Skills auf. Verwende stattdessen /{name}." -#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 +#: src/iac_code/ui/repl.py:874 src/iac_code/ui/repl.py:922 #, python-brace-format msgid "Command error: {error}" msgstr "Befehlsfehler: {error}" -#: src/iac_code/ui/repl.py:880 +#: src/iac_code/ui/repl.py:881 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Kein Handler für Befehl: {name}" -#: src/iac_code/ui/repl.py:1155 +#: src/iac_code/ui/repl.py:1156 msgid "Goodbye!" msgstr "Auf Wiedersehen!" -#: src/iac_code/ui/repl.py:1156 +#: src/iac_code/ui/repl.py:1157 msgid "Resume this session with:" msgstr "Diese Sitzung fortsetzen mit:" -#: src/iac_code/ui/repl.py:1159 +#: src/iac_code/ui/repl.py:1160 msgid "Session ID" msgstr "Sitzungs-ID" -#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 +#: src/iac_code/ui/repl.py:1210 src/iac_code/ui/repl.py:1214 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sitzung nicht gefunden: {session_id}" -#: src/iac_code/ui/repl.py:1262 +#: src/iac_code/ui/repl.py:1263 msgid "Session name: " msgstr "Sitzungsname: " -#: src/iac_code/ui/repl.py:1268 +#: src/iac_code/ui/repl.py:1269 msgid "Session name cannot be empty." msgstr "Der Sitzungsname darf nicht leer sein." -#: src/iac_code/ui/repl.py:1280 +#: src/iac_code/ui/repl.py:1279 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2444,23 +2444,23 @@ msgstr "" "Zum Fortsetzen ausführen:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1286 +#: src/iac_code/ui/repl.py:1283 msgid "Multiple sessions match. Resume one by ID:" msgstr "Mehrere Sitzungen passen. Setzen Sie eine per ID fort:" -#: src/iac_code/ui/repl.py:1401 +#: src/iac_code/ui/repl.py:1396 msgid "This conversation is from a different directory." msgstr "Diese Konversation stammt aus einem anderen Verzeichnis." -#: src/iac_code/ui/repl.py:1403 +#: src/iac_code/ui/repl.py:1398 msgid "To resume, run:" msgstr "Zum Fortsetzen ausführen:" -#: src/iac_code/ui/repl.py:1408 +#: src/iac_code/ui/repl.py:1403 msgid "(Command copied to clipboard)" msgstr "(Befehl in die Zwischenablage kopiert)" -#: src/iac_code/ui/repl.py:1565 +#: src/iac_code/ui/repl.py:1560 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2469,12 +2469,12 @@ msgstr "" "Das aktuelle Modell {model} unterstützt keine Bildeingabe. Verwenden Sie " "/model, um zu einem Vision-fähigen Modell zu wechseln." -#: src/iac_code/ui/repl.py:1574 +#: src/iac_code/ui/repl.py:1569 #, python-brace-format msgid "Image error: {err}" msgstr "Bildfehler: {err}" -#: src/iac_code/ui/repl.py:1591 +#: src/iac_code/ui/repl.py:1586 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 51c1add..5e591b9 100644 --- a/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.1\n" +"Project-Id-Version: iac-code 0.4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-03 11:39+0800\n" +"POT-Creation-Date: 2026-06-03 13:32+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: es\n" @@ -149,7 +149,7 @@ msgid "Renamed session to {name}" msgstr "Sesión renombrada a {name}" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:813 src/iac_code/ui/repl.py:827 msgid "Permission denied." msgstr "Permiso denegado." @@ -1592,7 +1592,7 @@ msgstr "" "sesión de Alibaba Cloud e iníciela de nuevo si es necesario, y ejecute " "/auth para elegir de nuevo Inicio de sesión OAuth (navegador)." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:842 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "" @@ -2327,70 +2327,70 @@ msgstr "No, siempre denegar \"{rule}\" (esta sesión)" msgid "No, always reject this tool" msgstr "No, rechazar siempre esta herramienta" -#: src/iac_code/ui/repl.py:423 +#: src/iac_code/ui/repl.py:424 msgid "Press Ctrl+C again to exit." msgstr "Pulse Ctrl+C de nuevo para salir." -#: src/iac_code/ui/repl.py:448 +#: src/iac_code/ui/repl.py:449 msgid "Interrupted." msgstr "Interrumpido." -#: src/iac_code/ui/repl.py:507 +#: src/iac_code/ui/repl.py:508 msgid "Update now" msgstr "Actualizar ahora" -#: src/iac_code/ui/repl.py:509 +#: src/iac_code/ui/repl.py:510 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Ejecuta el comando de actualización mostrado y sale cuando finalice " "correctamente." -#: src/iac_code/ui/repl.py:512 +#: src/iac_code/ui/repl.py:513 msgid "Skip" msgstr "Omitir" -#: src/iac_code/ui/repl.py:514 +#: src/iac_code/ui/repl.py:515 msgid "Continue with the current version for this session." msgstr "Continuar con la versión actual durante esta sesión." -#: src/iac_code/ui/repl.py:517 +#: src/iac_code/ui/repl.py:518 msgid "Skip until next version" msgstr "Omitir hasta la siguiente versión" -#: src/iac_code/ui/repl.py:519 +#: src/iac_code/ui/repl.py:520 msgid "Hide this update until a newer version is available." msgstr "" "Ocultar esta actualización hasta que haya una versión más nueva " "disponible." -#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 +#: src/iac_code/ui/repl.py:539 src/iac_code/ui/repl.py:551 msgid "Update command failed. Continuing with the current version." msgstr "El comando de actualización falló. Se continuará con la versión actual." -#: src/iac_code/ui/repl.py:543 +#: src/iac_code/ui/repl.py:544 msgid "Update completed. Restart iac-code to continue." msgstr "Actualización completada. Reinicia iac-code para continuar." -#: src/iac_code/ui/repl.py:581 +#: src/iac_code/ui/repl.py:582 msgid "No image in clipboard." msgstr "No hay ninguna imagen en el portapapeles." -#: src/iac_code/ui/repl.py:767 +#: src/iac_code/ui/repl.py:768 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:774 +#: src/iac_code/ui/repl.py:775 msgid "Shell command support is unavailable." msgstr "La compatibilidad con comandos de shell no está disponible." -#: src/iac_code/ui/repl.py:844 +#: src/iac_code/ui/repl.py:845 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Habilidad desconocida: ${name}. Escribe / para listar comandos y " "habilidades." -#: src/iac_code/ui/repl.py:846 +#: src/iac_code/ui/repl.py:847 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" @@ -2398,47 +2398,47 @@ msgstr "" "command: /{name}. Type /help for available commands.Comando desconocido: " "/{name}. Escriba /help para ver los comandos disponibles." -#: src/iac_code/ui/repl.py:851 +#: src/iac_code/ui/repl.py:852 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ solo invoca habilidades. Usa /{name} en su lugar." -#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 +#: src/iac_code/ui/repl.py:874 src/iac_code/ui/repl.py:922 #, python-brace-format msgid "Command error: {error}" msgstr "Error de comando: {error}" -#: src/iac_code/ui/repl.py:880 +#: src/iac_code/ui/repl.py:881 #, python-brace-format msgid "Command has no handler: {name}" msgstr "El comando no tiene controlador: {name}" -#: src/iac_code/ui/repl.py:1155 +#: src/iac_code/ui/repl.py:1156 msgid "Goodbye!" msgstr "¡Hasta luego!" -#: src/iac_code/ui/repl.py:1156 +#: src/iac_code/ui/repl.py:1157 msgid "Resume this session with:" msgstr "Para reanudar esta sesión, ejecute:" -#: src/iac_code/ui/repl.py:1159 +#: src/iac_code/ui/repl.py:1160 msgid "Session ID" msgstr "ID de sesión" -#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 +#: src/iac_code/ui/repl.py:1210 src/iac_code/ui/repl.py:1214 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sesión no encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1262 +#: src/iac_code/ui/repl.py:1263 msgid "Session name: " msgstr "Nombre de sesión: " -#: src/iac_code/ui/repl.py:1268 +#: src/iac_code/ui/repl.py:1269 msgid "Session name cannot be empty." msgstr "El nombre de sesión no puede estar vacío." -#: src/iac_code/ui/repl.py:1280 +#: src/iac_code/ui/repl.py:1279 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2449,23 +2449,23 @@ msgstr "" "Para reanudar, ejecute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1286 +#: src/iac_code/ui/repl.py:1283 msgid "Multiple sessions match. Resume one by ID:" msgstr "Varias sesiones coinciden. Reanuda una por ID:" -#: src/iac_code/ui/repl.py:1401 +#: src/iac_code/ui/repl.py:1396 msgid "This conversation is from a different directory." msgstr "Esta conversación procede de otro directorio." -#: src/iac_code/ui/repl.py:1403 +#: src/iac_code/ui/repl.py:1398 msgid "To resume, run:" msgstr "Para reanudar, ejecute:" -#: src/iac_code/ui/repl.py:1408 +#: src/iac_code/ui/repl.py:1403 msgid "(Command copied to clipboard)" msgstr "(Comando copiado al portapapeles)" -#: src/iac_code/ui/repl.py:1565 +#: src/iac_code/ui/repl.py:1560 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2474,12 +2474,12 @@ msgstr "" "El modelo actual {model} no admite entrada de imágenes. Usa /model para " "cambiar a un modelo con capacidad de visión." -#: src/iac_code/ui/repl.py:1574 +#: src/iac_code/ui/repl.py:1569 #, python-brace-format msgid "Image error: {err}" msgstr "Error de imagen: {err}" -#: src/iac_code/ui/repl.py:1591 +#: src/iac_code/ui/repl.py:1586 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 a5bb319..5f8800a 100644 --- a/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.1\n" +"Project-Id-Version: iac-code 0.4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-03 11:39+0800\n" +"POT-Creation-Date: 2026-06-03 13:32+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: fr\n" @@ -146,7 +146,7 @@ msgid "Renamed session to {name}" msgstr "Session renommée en {name}" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:813 src/iac_code/ui/repl.py:827 msgid "Permission denied." msgstr "Permission refusée." @@ -1598,7 +1598,7 @@ msgstr "" " si nécessaire, puis exécutez /auth pour choisir de nouveau Connexion " "OAuth (navigateur)." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:842 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "La compétence « {name} » est désactivée. Exécutez /skills pour l'activer." @@ -2332,115 +2332,115 @@ msgstr "Non, toujours refuser \"{rule}\" (cette session)" msgid "No, always reject this tool" msgstr "Non, toujours refuser cet outil" -#: src/iac_code/ui/repl.py:423 +#: src/iac_code/ui/repl.py:424 msgid "Press Ctrl+C again to exit." msgstr "Appuyez de nouveau sur Ctrl+C pour quitter." -#: src/iac_code/ui/repl.py:448 +#: src/iac_code/ui/repl.py:449 msgid "Interrupted." msgstr "Interrompu." -#: src/iac_code/ui/repl.py:507 +#: src/iac_code/ui/repl.py:508 msgid "Update now" msgstr "Mettre à jour maintenant" -#: src/iac_code/ui/repl.py:509 +#: src/iac_code/ui/repl.py:510 msgid "Run the shown update command and exit when it succeeds." msgstr "Exécute la commande de mise à jour affichée et quitte en cas de succès." -#: src/iac_code/ui/repl.py:512 +#: src/iac_code/ui/repl.py:513 msgid "Skip" msgstr "Ignorer" -#: src/iac_code/ui/repl.py:514 +#: src/iac_code/ui/repl.py:515 msgid "Continue with the current version for this session." msgstr "Continuer avec la version actuelle pour cette session." -#: src/iac_code/ui/repl.py:517 +#: src/iac_code/ui/repl.py:518 msgid "Skip until next version" msgstr "Ignorer jusqu’à la prochaine version" -#: src/iac_code/ui/repl.py:519 +#: src/iac_code/ui/repl.py:520 msgid "Hide this update until a newer version is available." msgstr "" "Masquer cette mise à jour jusqu’à ce qu’une version plus récente soit " "disponible." -#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 +#: src/iac_code/ui/repl.py:539 src/iac_code/ui/repl.py:551 msgid "Update command failed. Continuing with the current version." msgstr "La commande de mise à jour a échoué. La version actuelle sera conservée." -#: src/iac_code/ui/repl.py:543 +#: src/iac_code/ui/repl.py:544 msgid "Update completed. Restart iac-code to continue." msgstr "Mise à jour terminée. Redémarrez iac-code pour continuer." -#: src/iac_code/ui/repl.py:581 +#: src/iac_code/ui/repl.py:582 msgid "No image in clipboard." msgstr "Aucune image dans le presse-papiers." -#: src/iac_code/ui/repl.py:767 +#: src/iac_code/ui/repl.py:768 msgid "Usage: !" msgstr "Utilisation : !" -#: src/iac_code/ui/repl.py:774 +#: src/iac_code/ui/repl.py:775 msgid "Shell command support is unavailable." msgstr "La prise en charge des commandes shell n'est pas disponible." -#: src/iac_code/ui/repl.py:844 +#: src/iac_code/ui/repl.py:845 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Compétence inconnue : ${name}. Tapez / pour lister les commandes et les " "compétences." -#: src/iac_code/ui/repl.py:846 +#: src/iac_code/ui/repl.py:847 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available commands.Commande " "inconnue : /{name}. Saisissez /help pour la liste des commandes." -#: src/iac_code/ui/repl.py:851 +#: src/iac_code/ui/repl.py:852 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ n'invoque que des compétences. Utilisez plutôt /{name}." -#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 +#: src/iac_code/ui/repl.py:874 src/iac_code/ui/repl.py:922 #, python-brace-format msgid "Command error: {error}" msgstr "Erreur de commande : {error}" -#: src/iac_code/ui/repl.py:880 +#: src/iac_code/ui/repl.py:881 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Aucun gestionnaire pour la commande : {name}" -#: src/iac_code/ui/repl.py:1155 +#: src/iac_code/ui/repl.py:1156 msgid "Goodbye!" msgstr "Au revoir !" -#: src/iac_code/ui/repl.py:1156 +#: src/iac_code/ui/repl.py:1157 msgid "Resume this session with:" msgstr "Pour reprendre cette session :" -#: src/iac_code/ui/repl.py:1159 +#: src/iac_code/ui/repl.py:1160 msgid "Session ID" msgstr "ID de session" -#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 +#: src/iac_code/ui/repl.py:1210 src/iac_code/ui/repl.py:1214 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Session introuvable : {session_id}" -#: src/iac_code/ui/repl.py:1262 +#: src/iac_code/ui/repl.py:1263 msgid "Session name: " msgstr "Nom de session : " -#: src/iac_code/ui/repl.py:1268 +#: src/iac_code/ui/repl.py:1269 msgid "Session name cannot be empty." msgstr "Le nom de session ne peut pas être vide." -#: src/iac_code/ui/repl.py:1280 +#: src/iac_code/ui/repl.py:1279 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2451,23 +2451,23 @@ msgstr "" "Pour la reprendre, exécutez :\n" " {cmd}" -#: src/iac_code/ui/repl.py:1286 +#: src/iac_code/ui/repl.py:1283 msgid "Multiple sessions match. Resume one by ID:" msgstr "Plusieurs sessions correspondent. Reprenez-en une par ID :" -#: src/iac_code/ui/repl.py:1401 +#: src/iac_code/ui/repl.py:1396 msgid "This conversation is from a different directory." msgstr "Cette conversation provient d’un autre répertoire." -#: src/iac_code/ui/repl.py:1403 +#: src/iac_code/ui/repl.py:1398 msgid "To resume, run:" msgstr "Pour reprendre, exécutez :" -#: src/iac_code/ui/repl.py:1408 +#: src/iac_code/ui/repl.py:1403 msgid "(Command copied to clipboard)" msgstr "(Commande copiée dans le presse-papiers)" -#: src/iac_code/ui/repl.py:1565 +#: src/iac_code/ui/repl.py:1560 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2476,12 +2476,12 @@ msgstr "" "Le modèle actuel {model} ne prend pas en charge l’entrée d’image. " "Utilisez /model pour passer à un modèle compatible vision." -#: src/iac_code/ui/repl.py:1574 +#: src/iac_code/ui/repl.py:1569 #, python-brace-format msgid "Image error: {err}" msgstr "Erreur d’image : {err}" -#: src/iac_code/ui/repl.py:1591 +#: src/iac_code/ui/repl.py:1586 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 d0c70aa..105e84f 100644 --- a/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.1\n" +"Project-Id-Version: iac-code 0.4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-03 11:39+0800\n" +"POT-Creation-Date: 2026-06-03 13:32+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: ja\n" @@ -140,7 +140,7 @@ msgid "Renamed session to {name}" msgstr "セッション名を {name} に変更しました" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:813 src/iac_code/ui/repl.py:827 msgid "Permission denied." msgstr "権限が拒否されました。" @@ -1555,7 +1555,7 @@ msgstr "" "ロールに割り当ててください。ユーザーグループはサポートされていません。その後、古い認可ページを閉じ、必要に応じて Alibaba Cloud " "からサインアウトしてサインインし直し、/auth を実行して OAuth ログイン(ブラウザー)を再度選択してください。" -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:842 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "スキル「{name}」は無効です。有効にするには /skills を実行してください。" @@ -2269,111 +2269,111 @@ msgstr "いいえ、常に \"{rule}\" を拒否(このセッション)" msgid "No, always reject this tool" msgstr "いいえ、このツールは常に拒否" -#: src/iac_code/ui/repl.py:423 +#: src/iac_code/ui/repl.py:424 msgid "Press Ctrl+C again to exit." msgstr "終了するには Ctrl+C をもう一度押してください。" -#: src/iac_code/ui/repl.py:448 +#: src/iac_code/ui/repl.py:449 msgid "Interrupted." msgstr "中断しました。" -#: src/iac_code/ui/repl.py:507 +#: src/iac_code/ui/repl.py:508 msgid "Update now" msgstr "今すぐ更新" -#: src/iac_code/ui/repl.py:509 +#: src/iac_code/ui/repl.py:510 msgid "Run the shown update command and exit when it succeeds." msgstr "表示された更新コマンドを実行し、成功したら終了します。" -#: src/iac_code/ui/repl.py:512 +#: src/iac_code/ui/repl.py:513 msgid "Skip" msgstr "スキップ" -#: src/iac_code/ui/repl.py:514 +#: src/iac_code/ui/repl.py:515 msgid "Continue with the current version for this session." msgstr "このセッションでは現在のバージョンを使い続けます。" -#: src/iac_code/ui/repl.py:517 +#: src/iac_code/ui/repl.py:518 msgid "Skip until next version" msgstr "次のバージョンまでスキップ" -#: src/iac_code/ui/repl.py:519 +#: src/iac_code/ui/repl.py:520 msgid "Hide this update until a newer version is available." msgstr "より新しいバージョンが利用可能になるまで、この更新を非表示にします。" -#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 +#: src/iac_code/ui/repl.py:539 src/iac_code/ui/repl.py:551 msgid "Update command failed. Continuing with the current version." msgstr "更新コマンドに失敗しました。現在のバージョンで続行します。" -#: src/iac_code/ui/repl.py:543 +#: src/iac_code/ui/repl.py:544 msgid "Update completed. Restart iac-code to continue." msgstr "更新が完了しました。続行するには iac-code を再起動してください。" -#: src/iac_code/ui/repl.py:581 +#: src/iac_code/ui/repl.py:582 msgid "No image in clipboard." msgstr "クリップボードに画像がありません。" -#: src/iac_code/ui/repl.py:767 +#: src/iac_code/ui/repl.py:768 msgid "Usage: !" msgstr "使用方法: !" -#: src/iac_code/ui/repl.py:774 +#: src/iac_code/ui/repl.py:775 msgid "Shell command support is unavailable." msgstr "シェルコマンドのサポートは利用できません。" -#: src/iac_code/ui/repl.py:844 +#: src/iac_code/ui/repl.py:845 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "不明なスキル: ${name}。/ を入力するとコマンドとスキルを一覧表示します。" -#: src/iac_code/ui/repl.py:846 +#: src/iac_code/ui/repl.py:847 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "" "Unknown command: /{name}. Type /help for available " "commands.不明なコマンドです:/{name}。利用可能なコマンドは /help を入力してください。" -#: src/iac_code/ui/repl.py:851 +#: src/iac_code/ui/repl.py:852 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ はスキルのみを呼び出します。代わりに /{name} を使用してください。" -#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 +#: src/iac_code/ui/repl.py:874 src/iac_code/ui/repl.py:922 #, python-brace-format msgid "Command error: {error}" msgstr "コマンドエラー:{error}" -#: src/iac_code/ui/repl.py:880 +#: src/iac_code/ui/repl.py:881 #, python-brace-format msgid "Command has no handler: {name}" msgstr "ハンドラーがないコマンドです:{name}" -#: src/iac_code/ui/repl.py:1155 +#: src/iac_code/ui/repl.py:1156 msgid "Goodbye!" msgstr "さようなら。" -#: src/iac_code/ui/repl.py:1156 +#: src/iac_code/ui/repl.py:1157 msgid "Resume this session with:" msgstr "このセッションを再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:1159 +#: src/iac_code/ui/repl.py:1160 msgid "Session ID" msgstr "セッション ID" -#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 +#: src/iac_code/ui/repl.py:1210 src/iac_code/ui/repl.py:1214 #, python-brace-format msgid "Session not found: {session_id}" msgstr "セッションが見つかりません:{session_id}" -#: src/iac_code/ui/repl.py:1262 +#: src/iac_code/ui/repl.py:1263 msgid "Session name: " msgstr "セッション名: " -#: src/iac_code/ui/repl.py:1268 +#: src/iac_code/ui/repl.py:1269 msgid "Session name cannot be empty." msgstr "セッション名は空にできません。" -#: src/iac_code/ui/repl.py:1280 +#: src/iac_code/ui/repl.py:1279 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2384,35 +2384,35 @@ msgstr "" "再開するには次を実行してください:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1286 +#: src/iac_code/ui/repl.py:1283 msgid "Multiple sessions match. Resume one by ID:" msgstr "複数のセッションが一致しました。ID でいずれかを再開してください:" -#: src/iac_code/ui/repl.py:1401 +#: src/iac_code/ui/repl.py:1396 msgid "This conversation is from a different directory." msgstr "この会話は別のディレクトリ由来です。" -#: src/iac_code/ui/repl.py:1403 +#: src/iac_code/ui/repl.py:1398 msgid "To resume, run:" msgstr "再開するには次を実行してください:" -#: src/iac_code/ui/repl.py:1408 +#: src/iac_code/ui/repl.py:1403 msgid "(Command copied to clipboard)" msgstr "(コマンドをクリップボードにコピーしました)" -#: src/iac_code/ui/repl.py:1565 +#: src/iac_code/ui/repl.py:1560 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " "to a vision-capable model." msgstr "現在のモデル {model} は画像入力をサポートしていません。/model を使用してビジョン対応モデルに切り替えてください。" -#: src/iac_code/ui/repl.py:1574 +#: src/iac_code/ui/repl.py:1569 #, python-brace-format msgid "Image error: {err}" msgstr "画像エラー:{err}" -#: src/iac_code/ui/repl.py:1591 +#: src/iac_code/ui/repl.py:1586 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 4186954..7858875 100644 --- a/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.1\n" +"Project-Id-Version: iac-code 0.4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-03 11:39+0800\n" +"POT-Creation-Date: 2026-06-03 13:32+0800\n" "PO-Revision-Date: 2026-05-13 00:00+0000\n" "Last-Translator: \n" "Language: pt\n" @@ -146,7 +146,7 @@ msgid "Renamed session to {name}" msgstr "Sessão renomeada para {name}" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:813 src/iac_code/ui/repl.py:827 msgid "Permission denied." msgstr "Permissão negada." @@ -1584,7 +1584,7 @@ msgstr "" "entre novamente se necessário, e execute /auth para escolher Login OAuth " "(navegador) novamente." -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:842 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "A habilidade '{name}' está desativada. Execute /skills para ativá-la." @@ -2310,113 +2310,113 @@ msgstr "Não, sempre negar \"{rule}\" (esta sessão)" msgid "No, always reject this tool" msgstr "Não, sempre rejeitar esta ferramenta" -#: src/iac_code/ui/repl.py:423 +#: src/iac_code/ui/repl.py:424 msgid "Press Ctrl+C again to exit." msgstr "Pressione Ctrl+C novamente para sair." -#: src/iac_code/ui/repl.py:448 +#: src/iac_code/ui/repl.py:449 msgid "Interrupted." msgstr "Interrompido." -#: src/iac_code/ui/repl.py:507 +#: src/iac_code/ui/repl.py:508 msgid "Update now" msgstr "Atualizar agora" -#: src/iac_code/ui/repl.py:509 +#: src/iac_code/ui/repl.py:510 msgid "Run the shown update command and exit when it succeeds." msgstr "" "Executa o comando de atualização mostrado e sai quando ele for concluído " "com sucesso." -#: src/iac_code/ui/repl.py:512 +#: src/iac_code/ui/repl.py:513 msgid "Skip" msgstr "Ignorar" -#: src/iac_code/ui/repl.py:514 +#: src/iac_code/ui/repl.py:515 msgid "Continue with the current version for this session." msgstr "Continuar com a versão atual nesta sessão." -#: src/iac_code/ui/repl.py:517 +#: src/iac_code/ui/repl.py:518 msgid "Skip until next version" msgstr "Ignorar até a próxima versão" -#: src/iac_code/ui/repl.py:519 +#: src/iac_code/ui/repl.py:520 msgid "Hide this update until a newer version is available." msgstr "Ocultar esta atualização até que uma versão mais nova esteja disponível." -#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 +#: src/iac_code/ui/repl.py:539 src/iac_code/ui/repl.py:551 msgid "Update command failed. Continuing with the current version." msgstr "O comando de atualização falhou. Continuando com a versão atual." -#: src/iac_code/ui/repl.py:543 +#: src/iac_code/ui/repl.py:544 msgid "Update completed. Restart iac-code to continue." msgstr "Atualização concluída. Reinicie o iac-code para continuar." -#: src/iac_code/ui/repl.py:581 +#: src/iac_code/ui/repl.py:582 msgid "No image in clipboard." msgstr "Nenhuma imagem na área de transferência." -#: src/iac_code/ui/repl.py:767 +#: src/iac_code/ui/repl.py:768 msgid "Usage: !" msgstr "Uso: !" -#: src/iac_code/ui/repl.py:774 +#: src/iac_code/ui/repl.py:775 msgid "Shell command support is unavailable." msgstr "O suporte a comandos shell não está disponível." -#: src/iac_code/ui/repl.py:844 +#: src/iac_code/ui/repl.py:845 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "" "Habilidade desconhecida: ${name}. Digite / para listar comandos e " "habilidades." -#: src/iac_code/ui/repl.py:846 +#: src/iac_code/ui/repl.py:847 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "Comando desconhecido: /{name}. Digite /help para ver os comandos." -#: src/iac_code/ui/repl.py:851 +#: src/iac_code/ui/repl.py:852 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ invoca apenas habilidades. Use /{name} em vez disso." -#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 +#: src/iac_code/ui/repl.py:874 src/iac_code/ui/repl.py:922 #, python-brace-format msgid "Command error: {error}" msgstr "Erro de comando: {error}" -#: src/iac_code/ui/repl.py:880 +#: src/iac_code/ui/repl.py:881 #, python-brace-format msgid "Command has no handler: {name}" msgstr "Comando sem tratador: {name}" -#: src/iac_code/ui/repl.py:1155 +#: src/iac_code/ui/repl.py:1156 msgid "Goodbye!" msgstr "Até logo!" -#: src/iac_code/ui/repl.py:1156 +#: src/iac_code/ui/repl.py:1157 msgid "Resume this session with:" msgstr "Para retomar esta sessão, execute:" -#: src/iac_code/ui/repl.py:1159 +#: src/iac_code/ui/repl.py:1160 msgid "Session ID" msgstr "ID da sessão" -#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 +#: src/iac_code/ui/repl.py:1210 src/iac_code/ui/repl.py:1214 #, python-brace-format msgid "Session not found: {session_id}" msgstr "Sessão não encontrada: {session_id}" -#: src/iac_code/ui/repl.py:1262 +#: src/iac_code/ui/repl.py:1263 msgid "Session name: " msgstr "Nome da sessão: " -#: src/iac_code/ui/repl.py:1268 +#: src/iac_code/ui/repl.py:1269 msgid "Session name cannot be empty." msgstr "O nome da sessão não pode estar vazio." -#: src/iac_code/ui/repl.py:1280 +#: src/iac_code/ui/repl.py:1279 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2427,23 +2427,23 @@ msgstr "" "Para retomar, execute:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1286 +#: src/iac_code/ui/repl.py:1283 msgid "Multiple sessions match. Resume one by ID:" msgstr "Várias sessões correspondem. Retome uma por ID:" -#: src/iac_code/ui/repl.py:1401 +#: src/iac_code/ui/repl.py:1396 msgid "This conversation is from a different directory." msgstr "Esta conversa é de outro diretório." -#: src/iac_code/ui/repl.py:1403 +#: src/iac_code/ui/repl.py:1398 msgid "To resume, run:" msgstr "Para retomar, execute:" -#: src/iac_code/ui/repl.py:1408 +#: src/iac_code/ui/repl.py:1403 msgid "(Command copied to clipboard)" msgstr "(Comando copiado para a área de transferência)" -#: src/iac_code/ui/repl.py:1565 +#: src/iac_code/ui/repl.py:1560 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " @@ -2452,12 +2452,12 @@ msgstr "" "O modelo atual {model} não suporta entrada de imagem. Use /model para " "alternar para um modelo com capacidade de visão." -#: src/iac_code/ui/repl.py:1574 +#: src/iac_code/ui/repl.py:1569 #, python-brace-format msgid "Image error: {err}" msgstr "Erro de imagem: {err}" -#: src/iac_code/ui/repl.py:1591 +#: src/iac_code/ui/repl.py:1586 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." 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 72681df..83ca420 100644 --- a/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +++ b/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: iac-code 0.3.1\n" +"Project-Id-Version: iac-code 0.4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-03 11:39+0800\n" +"POT-Creation-Date: 2026-06-03 13:32+0800\n" "PO-Revision-Date: 2026-04-02 00:00+0000\n" "Last-Translator: \n" "Language: zh\n" @@ -136,7 +136,7 @@ msgid "Renamed session to {name}" msgstr "已将会话重命名为 {name}" #: src/iac_code/agent/agent_loop.py:413 src/iac_code/agent/agent_loop.py:428 -#: src/iac_code/ui/repl.py:812 src/iac_code/ui/repl.py:826 +#: src/iac_code/ui/repl.py:813 src/iac_code/ui/repl.py:827 msgid "Permission denied." msgstr "权限被拒绝。" @@ -1533,7 +1533,7 @@ msgstr "" " RAM 角色。不支持用户组。然后关闭旧的授权页面,必要时退出 Alibaba Cloud 并重新登录,再运行 /auth 重新选择 OAuth " "登录(浏览器)。" -#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:841 +#: src/iac_code/skills/skill_tool.py:85 src/iac_code/ui/repl.py:842 #, python-brace-format msgid "Skill '{name}' is disabled. Run /skills to enable it." msgstr "技能“{name}”已禁用。运行 /skills 以启用它。" @@ -2243,109 +2243,109 @@ msgstr "否,始终拒绝 \"{rule}\"(本次会话)" msgid "No, always reject this tool" msgstr "否,始终拒绝此工具" -#: src/iac_code/ui/repl.py:423 +#: src/iac_code/ui/repl.py:424 msgid "Press Ctrl+C again to exit." msgstr "再次按 Ctrl+C 退出。" -#: src/iac_code/ui/repl.py:448 +#: src/iac_code/ui/repl.py:449 msgid "Interrupted." msgstr "已中断。" -#: src/iac_code/ui/repl.py:507 +#: src/iac_code/ui/repl.py:508 msgid "Update now" msgstr "立即更新" -#: src/iac_code/ui/repl.py:509 +#: src/iac_code/ui/repl.py:510 msgid "Run the shown update command and exit when it succeeds." msgstr "运行显示的更新命令,成功后退出。" -#: src/iac_code/ui/repl.py:512 +#: src/iac_code/ui/repl.py:513 msgid "Skip" msgstr "跳过" -#: src/iac_code/ui/repl.py:514 +#: src/iac_code/ui/repl.py:515 msgid "Continue with the current version for this session." msgstr "本次会话继续使用当前版本。" -#: src/iac_code/ui/repl.py:517 +#: src/iac_code/ui/repl.py:518 msgid "Skip until next version" msgstr "跳过直到下一个版本" -#: src/iac_code/ui/repl.py:519 +#: src/iac_code/ui/repl.py:520 msgid "Hide this update until a newer version is available." msgstr "隐藏此更新,直到有更新的版本可用。" -#: src/iac_code/ui/repl.py:538 src/iac_code/ui/repl.py:550 +#: src/iac_code/ui/repl.py:539 src/iac_code/ui/repl.py:551 msgid "Update command failed. Continuing with the current version." msgstr "更新命令失败。将继续使用当前版本。" -#: src/iac_code/ui/repl.py:543 +#: src/iac_code/ui/repl.py:544 msgid "Update completed. Restart iac-code to continue." msgstr "更新已完成。请重启 iac-code 以继续。" -#: src/iac_code/ui/repl.py:581 +#: src/iac_code/ui/repl.py:582 msgid "No image in clipboard." msgstr "剪贴板中没有图像。" -#: src/iac_code/ui/repl.py:767 +#: src/iac_code/ui/repl.py:768 msgid "Usage: !" msgstr "用法:!" -#: src/iac_code/ui/repl.py:774 +#: src/iac_code/ui/repl.py:775 msgid "Shell command support is unavailable." msgstr "Shell 命令支持不可用。" -#: src/iac_code/ui/repl.py:844 +#: src/iac_code/ui/repl.py:845 #, python-brace-format msgid "Unknown skill: ${name}. Type / to list commands and skills." msgstr "未知技能:${name}。输入 / 可列出命令和技能。" -#: src/iac_code/ui/repl.py:846 +#: src/iac_code/ui/repl.py:847 #, python-brace-format msgid "Unknown command: /{name}. Type /help for available commands." msgstr "未知命令:/{name}。输入 /help 查看可用命令。" -#: src/iac_code/ui/repl.py:851 +#: src/iac_code/ui/repl.py:852 #, python-brace-format msgid "$ only invokes skills. Use /{name} instead." msgstr "$ 只能调用技能。请改用 /{name}。" -#: src/iac_code/ui/repl.py:873 src/iac_code/ui/repl.py:921 +#: src/iac_code/ui/repl.py:874 src/iac_code/ui/repl.py:922 #, python-brace-format msgid "Command error: {error}" msgstr "命令错误:{error}" -#: src/iac_code/ui/repl.py:880 +#: src/iac_code/ui/repl.py:881 #, python-brace-format msgid "Command has no handler: {name}" msgstr "命令没有处理器:{name}" -#: src/iac_code/ui/repl.py:1155 +#: src/iac_code/ui/repl.py:1156 msgid "Goodbye!" msgstr "再见!" -#: src/iac_code/ui/repl.py:1156 +#: src/iac_code/ui/repl.py:1157 msgid "Resume this session with:" msgstr "恢复此会话请运行:" -#: src/iac_code/ui/repl.py:1159 +#: src/iac_code/ui/repl.py:1160 msgid "Session ID" msgstr "会话 ID" -#: src/iac_code/ui/repl.py:1209 src/iac_code/ui/repl.py:1213 +#: src/iac_code/ui/repl.py:1210 src/iac_code/ui/repl.py:1214 #, python-brace-format msgid "Session not found: {session_id}" msgstr "会话不存在:{session_id}" -#: src/iac_code/ui/repl.py:1262 +#: src/iac_code/ui/repl.py:1263 msgid "Session name: " msgstr "会话名称:" -#: src/iac_code/ui/repl.py:1268 +#: src/iac_code/ui/repl.py:1269 msgid "Session name cannot be empty." msgstr "会话名称不能为空。" -#: src/iac_code/ui/repl.py:1280 +#: src/iac_code/ui/repl.py:1279 #, python-brace-format msgid "" "This session belongs to a different directory.\n" @@ -2356,35 +2356,35 @@ msgstr "" "请运行以下命令恢复:\n" " {cmd}" -#: src/iac_code/ui/repl.py:1286 +#: src/iac_code/ui/repl.py:1283 msgid "Multiple sessions match. Resume one by ID:" msgstr "匹配到多个会话。请通过 ID 恢复其中一个:" -#: src/iac_code/ui/repl.py:1401 +#: src/iac_code/ui/repl.py:1396 msgid "This conversation is from a different directory." msgstr "该会话来自另一个目录。" -#: src/iac_code/ui/repl.py:1403 +#: src/iac_code/ui/repl.py:1398 msgid "To resume, run:" msgstr "请运行以下命令恢复:" -#: src/iac_code/ui/repl.py:1408 +#: src/iac_code/ui/repl.py:1403 msgid "(Command copied to clipboard)" msgstr "(命令已复制到剪贴板)" -#: src/iac_code/ui/repl.py:1565 +#: src/iac_code/ui/repl.py:1560 #, python-brace-format msgid "" "Current model {model} does not support image input. Use /model to switch " "to a vision-capable model." msgstr "当前模型 {model} 不支持图像输入。请使用 /model 切换到支持视觉的模型。" -#: src/iac_code/ui/repl.py:1574 +#: src/iac_code/ui/repl.py:1569 #, python-brace-format msgid "Image error: {err}" msgstr "图像错误:{err}" -#: src/iac_code/ui/repl.py:1591 +#: src/iac_code/ui/repl.py:1586 msgid "" "Failed to persist image to cache; it will only exist in memory for this " "turn." diff --git a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/command-line-options.md b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/command-line-options.md index 00a3084..2b523ab 100644 --- a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/command-line-options.md +++ b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/command-line-options.md @@ -16,7 +16,7 @@ Befehlszeilenoptionen steuern, wie IaC Code gestartet wird. Sie können vor dem | `--output-format ` | Ausgabeformat für den nicht-interaktiven Modus festlegen. Unterstützte Werte sind `text`, `json` und `stream-json`. Standard ist `text`. | | `--max-turns ` | Maximale Anzahl der Agenten-Runden im nicht-interaktiven Modus begrenzen. Standard ist `100`. | | `-d`, `--debug` | Debug-Protokollierung für den aktuellen Lauf aktivieren. Im interaktiven Modus verwenden Sie `/debug`, um die Debug-Protokollierung nach dem Start zu prüfen oder zu ändern. | -| `-r `, `--resume ` | Eine vorherige Sitzung anhand der ID fortsetzen. Dies dient zum Zurückkehren zu einer bekannten Konversation. | +| `-r `, `--resume ` | Eine vorherige Sitzung über die exakte Sitzungs-ID, ein eindeutiges ID-Präfix oder einen eindeutigen Sitzungsnamen fortsetzen. Projektübergreifend aufgelöste Sitzungen geben einen `cd ... && iac-code --resume `-Befehl aus, statt das aktuelle Projekt direkt zu wechseln. | | `-c`, `--continue` | Die letzte Sitzung fortsetzen. Kann nicht zusammen mit `--resume` verwendet werden. | | `--allowed-tools ` | Kommagetrennte Werkzeug-Berechtigungsmuster zum Erlauben, z.B. `'bash(git *),write_file'`. | | `--disallowed-tools ` | Kommagetrennte Werkzeug-Berechtigungsmuster zum Verweigern, z.B. `'bash(rm *)'`. | diff --git a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/commands.md b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/commands.md index 73c326e..814a395 100644 --- a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/commands.md +++ b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/commands.md @@ -20,7 +20,11 @@ Text nach dem Befehlsnamen wird als Argumente uebergeben. In der folgenden Tabel | `/effort [level]` | Zeigen oder aendern Sie den Denkaufwand fuer das aktive Modell, wenn das ausgewaehlte Modell Aufwandsteuerung unterstuetzt. Mit einem Level wird der angeforderte Wert angewendet, wenn er fuer das Modell gueltig ist. Ohne Level wird im REPL eine interaktive Auswahl geoeffnet, oder der aktuelle Aufwand wird in nicht-interaktiven Kontexten ausgegeben. | | `/exit` | Beenden Sie das interaktive REPL. Aliase: `/quit`, `/q`. | | `/help` | Zeigen Sie verfuegbare Befehle und gaengige Tastenkuerzel im REPL an. Alias: `/?`. | +| `/memory [\|search \|delete \|help]` | Gespeicherte Erinnerungen auflisten, anzeigen, durchsuchen oder löschen. Das Erstellen von Erinnerungen in natürlicher Sprache erledigt weiterhin der Assistent über das Memory-Werkzeug, wenn Sie ihn bitten, sich etwas zu merken. | | `/model [model_name]` | Zeigen oder wechseln Sie das aktive Modell. Mit `model_name` wird direkt zu diesem Modell fuer den aktiven Anbieter gewechselt. Ohne Argument wird eine interaktive Modellauswahl geoeffnet, wenn ein Anbieter konfiguriert ist, oder das aktuelle Modell wird ausgegeben, wenn keine Konsolen-UI verfuegbar ist. | -| `/resume [conversation id or search term]` | Setzen Sie eine fruehere Sitzung fort. Mit einem Argument loest IaC Code es als Sitzungs-ID oder eindeutigen ID-Praefix auf. Ohne Argument wird die interaktive Sitzungsauswahl geoeffnet. Projektuebergreifende Sitzungen geben einen `cd ... && iac-code --resume ` Befehl aus, anstatt das aktuelle Projekt direkt zu wechseln. | +| `/rename ` | Die aktuelle Sitzung benennen. Namen erscheinen im Willkommensbanner, im Exit-Hinweis und in der `/resume`-Auswahl und können mit `/resume` oder `--resume` verwendet werden, wenn sie eine Sitzung eindeutig identifizieren. | +| `/resume [sitzungs-id\|eindeutiges-id-präfix\|eindeutiger-sitzungsname]` | Eine frühere Sitzung fortsetzen. Mit einem Argument löst IaC Code es als exakte Sitzungs-ID, eindeutiges ID-Präfix oder eindeutigen Sitzungsnamen auf. Ohne Argument wird die interaktive Sitzungsauswahl geöffnet. Projektübergreifende Sitzungen geben einen `cd ... && iac-code --resume `-Befehl aus, anstatt das aktuelle Projekt direkt zu wechseln. | +| `/skills` | Die Skill-Verwaltungsauswahl öffnen. Skills nach Name oder Beschreibung suchen, nach Name/Quelle/Größe sortieren und Benutzer- oder Projekt-Skills aktivieren oder deaktivieren. Gebündelte Skills bleiben gesperrt aktiviert. | +| `/status` | Aktuelle Sitzungs-ID, Anbieter, Modell, Alibaba Cloud-Region, Arbeitsverzeichnis, aufgezeichnete API-Token-Nutzung, Rundenzahl und Kontextauslastung anzeigen. | Die genaue Befehlsliste kann sich zwischen Versionen aendern. Verwenden Sie `/help` oder tippen Sie `/` im REPL, um die in Ihrer installierten Version verfuegbaren Befehle anzuzeigen. diff --git a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/interactive-mode.md b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/interactive-mode.md index 53255d1..aaabb00 100644 --- a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/interactive-mode.md +++ b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/interactive-mode.md @@ -25,6 +25,12 @@ Beschreiben Sie dann, was Sie erstellen moechten: Create a VPC, two ECS instances, and a security group that allows SSH from my office IP. ``` +## Befehle + +Tippen Sie `/`, um verfügbare Slash-Befehle zu entdecken. Zu den häufigen Betriebsbefehlen gehören `/status` für den aktuellen Sitzungszustand, `/skills` für die Skill-Verwaltung, `/memory` für gespeicherte Erinnerungen, `/rename` zum Benennen der aktiven Sitzung und `/resume` zum Wechseln zwischen Sitzungen. + +Tippen Sie `$`, um ausschließlich Skills zu entdecken und aufzurufen. + ## Eingabe bearbeiten Verwenden Sie `Shift+Enter`, um eine neue Zeile einzufuegen, ohne den Prompt zu senden. Druecken Sie normales `Enter`, um den vollstaendigen Prompt zu senden. diff --git a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/sessions.md b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/sessions.md index 2952b56..4d2f7bd 100644 --- a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/sessions.md +++ b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/sessions.md @@ -1,73 +1,90 @@ --- -title: Sessions -description: Persist and resume conversations across runs. +title: Sitzungen +description: Konversationen über Läufe hinweg speichern und fortsetzen. --- -# Sessions +# Sitzungen -IaC Code automatically persists every conversation to disk. You can resume any previous session to continue where you left off. +IaC Code speichert jede Konversation automatisch auf der Festplatte. Sie können frühere Sitzungen fortsetzen und dort weiterarbeiten, wo Sie aufgehört haben. -## Resuming Sessions +## Sitzungen fortsetzen -### Interactive: `/resume` +### Interaktiv: `/resume` -In the REPL, use the `/resume` command: +Verwenden Sie im REPL den Befehl `/resume`: ```text /resume ``` -This opens an interactive picker showing recent sessions for the current project, with their last prompt as the title. +Dies öffnet eine interaktive Auswahl mit den letzten Sitzungen des aktuellen Projekts. Wenn ein Sitzungsname gesetzt ist, wird er als Titel angezeigt; sonst wird die letzte Eingabe oder ersatzweise die erste Eingabe verwendet. -To resume a specific session by ID or ID prefix: +Eine bestimmte Sitzung können Sie über die exakte Sitzungs-ID, ein eindeutiges ID-Präfix oder einen eindeutigen Sitzungsnamen fortsetzen: ```text /resume abc123 ``` -### CLI: `--resume` and `--continue` +### Sitzungen benennen -Resume a specific session from the command line: +Verwenden Sie `/rename`, um der aktiven Sitzung einen stabilen, gut lesbaren Namen zu geben: + +```text +/rename deploy-prod +``` + +Der Name wird in den Sitzungsmetadaten gespeichert. Er erscheint beim Fortsetzen im Willkommensbanner, im Exit-Hinweis und in der `/resume`-Auswahl. + +Wenn der Name eine Sitzung eindeutig identifiziert, können Sie damit fortsetzen: + +```text +/resume deploy-prod +iac-code --resume deploy-prod +``` + +### CLI: `--resume` und `--continue` + +Eine bestimmte Sitzung können Sie über die Befehlszeile per exakter Sitzungs-ID, eindeutigem ID-Präfix oder eindeutigem Sitzungsnamen fortsetzen: ```bash -iac-code --resume +iac-code --resume ``` -Resume the most recent session: +Die zuletzt verwendete Sitzung fortsetzen: ```bash iac-code --continue ``` -The short flags `-r` and `-c` are also available: +Die Kurzoptionen `-r` und `-c` sind ebenfalls verfügbar: ```bash -iac-code -r +iac-code -r iac-code -c ``` -### Cross-project Sessions +### Projektübergreifende Sitzungen -When a session belongs to a different project directory, IaC Code does not hot-swap the working directory. Instead, it prints the command to resume in the correct context: +Wenn eine Sitzung zu einem anderen Projektverzeichnis gehört, wechselt IaC Code das Arbeitsverzeichnis nicht direkt. Stattdessen wird ein Befehl ausgegeben, der die Sitzung im richtigen Kontext fortsetzt: ```text cd /path/to/other/project && iac-code --resume ``` -This command is also copied to the clipboard when possible. +Dieser Befehl wird nach Möglichkeit auch in die Zwischenablage kopiert. -## Interruption Recovery +## Wiederherstellung nach Unterbrechungen -If a session was interrupted mid-execution (e.g., the process was killed while a tool was running), IaC Code detects the orphaned tool calls on resume and appends synthetic error results. This allows the model to recover gracefully without getting stuck waiting for tool output that will never arrive. +Wenn eine Sitzung mitten in der Ausführung unterbrochen wurde, etwa weil der Prozess während eines Tool-Laufs beendet wurde, erkennt IaC Code beim Fortsetzen verwaiste Tool-Aufrufe und ergänzt synthetische Fehlerergebnisse. So kann das Modell sauber weiterarbeiten, statt dauerhaft auf Tool-Ausgabe zu warten, die nie eintreffen wird. -## Session Picker +## Sitzungsauswahl -The `/resume` picker displays: +Die `/resume`-Auswahl zeigt: -| Column | Description | -|--------|-------------| -| Title | Last user prompt (or first prompt if no metadata) | -| Branch | Git branch at the time of the session | -| Time | Last modification time | +| Spalte | Beschreibung | +|--------|--------------| +| Titel | Sitzungsname, falls gesetzt; sonst letzte oder erste Benutzereingabe | +| Branch | Git-Branch zum Zeitpunkt der Sitzung | +| Zeit | Letzte Änderungszeit | -Sessions are sorted by most recent first. You can type to filter by title content. +Sitzungen werden absteigend nach Aktualität sortiert. Sie können Text eingeben, um nach dem Titelinhalt zu filtern. diff --git a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/skills.md b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/skills.md index bafcfd8..7733d0e 100644 --- a/website/i18n/de/docusaurus-plugin-content-docs/current/cli/skills.md +++ b/website/i18n/de/docusaurus-plugin-content-docs/current/cli/skills.md @@ -89,6 +89,14 @@ paths: | `agent` | No | `"general-purpose"` | Agent type for fork mode | | `paths` | No | `[]` | Glob patterns for path-based auto-activation | +## Skills verwalten + +Führen Sie `/skills` im interaktiven REPL aus, um die Skill-Verwaltungsauswahl zu öffnen. Die Auswahl zeigt gefundene gebündelte, Benutzer- und Projekt-Skills mit Quelle, Größe und Aktivierungsstatus. Sie können nach Name oder Beschreibung suchen, nach Name/Quelle/Größe sortieren und Benutzer- oder Projekt-Skills ein- und ausschalten. + +Deaktivierte Skills werden in `settings.yml` unter `disabled_skills` gespeichert. Gebündelte Skills sind fest aktiviert und werden nicht in die Deaktivierungsliste geschrieben. + +Verwenden Sie `$`, wenn Autovervollständigung und Aufruf nur auf Skills zielen sollen. Das ist nützlich, wenn ein Skill-Name sich mit normalem Text überschneidet oder wenn Sie eingebaute Slash-Befehle vermeiden möchten. + ## Execution Modes ### Inline (default) @@ -188,3 +196,4 @@ Save this as `~/.iac-code/skills/checklist.md` or `.iac-code/skills/checklist.md - **Bundled skills** are always allowed automatically. - **User/project skills** with no shell commands and no `allowed_tools` are auto-allowed. - **Other skills** prompt for user confirmation on first use. +- **Deaktivierte Benutzer-/Projekt-Skills** werden aus modell-sichtbaren Skill-Listen und automatischen Triggern ausgeblendet; direkte `skill`-Tool-Aufrufe geben einen Fehler für deaktivierte Skills zurück. diff --git a/website/i18n/de/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md b/website/i18n/de/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md index 98c728f..c8b46c7 100644 --- a/website/i18n/de/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md +++ b/website/i18n/de/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md @@ -7,6 +7,22 @@ description: Konfigurieren Sie Alibaba Cloud AccessKey- oder STS-Anmeldedaten. Alibaba Cloud-Anmeldedaten werden fuer Operationen benoetigt, die Cloud-Ressourcen ueberpruefen oder verwalten. +## OAuth-Browser-Anmeldung + +Der empfohlene interaktive Einrichtungsweg ist `/auth`: + +```text +/auth +``` + +Wählen Sie **IaC-Cloud-Service konfigurieren**, dann **Alibaba Cloud** und anschließend **OAuth Login (Browser)**. IaC Code öffnet einen Browser-Autorisierungsablauf, wartet auf den lokalen Callback, tauscht den Autorisierungscode mit PKCE aus und speichert OAuth-gestützte temporäre Anmeldedaten in `.cloud-credentials.yml` im IaC-Code-Konfigurationsverzeichnis. + +Während der Einrichtung können Sie die China- oder International-OAuth-Site wählen. IaC Code speichert die ausgewählte Site zusammen mit dem Refresh Token, damit spätere Aktualisierungen denselben Endpunkt verwenden. + +OAuth-Anmeldedaten werden automatisch aktualisiert, wenn Access Token oder STS-Anmeldedaten bald ablaufen. Wenn der Refresh Token abläuft oder widerrufen wird, führen Sie erneut `/auth` aus und wählen Sie OAuth Login (Browser). + +## Umgebungsvariablen + Unterstuetzte Umgebungsvariablen: | Variable | Beschreibung | diff --git a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/command-line-options.md b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/command-line-options.md index 0f364e0..50594eb 100644 --- a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/command-line-options.md +++ b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/command-line-options.md @@ -16,7 +16,7 @@ Las opciones de línea de comandos cambian cómo se inicia IaC Code. Úselas ant | `--output-format ` | Establecer el formato de salida para el modo no interactivo. Los valores soportados son `text`, `json` y `stream-json`. El valor predeterminado es `text`. | | `--max-turns ` | Limitar el número máximo de turnos del agente en modo no interactivo. El valor predeterminado es `100`. | | `-d`, `--debug` | Habilitar el registro de depuración para la ejecución actual. En modo interactivo, use `/debug` para inspeccionar o cambiar el registro de depuración después del inicio. | -| `-r `, `--resume ` | Reanudar una sesión anterior por ID. Esto es para volver a una conversación conocida. | +| `-r `, `--resume ` | Reanudar una sesión anterior por ID exacto, prefijo único de ID o nombre único de sesión. Las sesiones resueltas en otro proyecto imprimen un comando `cd ... && iac-code --resume ` en lugar de cambiar en caliente el proyecto actual. | | `-c`, `--continue` | Reanudar la sesión más reciente. No se puede usar junto con `--resume`. | | `--allowed-tools ` | Patrones de permisos de herramientas separados por comas para permitir, ej. `'bash(git *),write_file'`. | | `--disallowed-tools ` | Patrones de permisos de herramientas separados por comas para denegar, ej. `'bash(rm *)'`. | diff --git a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/commands.md b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/commands.md index 0eb28f0..964e210 100644 --- a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/commands.md +++ b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/commands.md @@ -20,7 +20,11 @@ El texto despues del nombre del comando se pasa como argumentos. En la tabla sig | `/effort [level]` | Muestra o cambia el esfuerzo de pensamiento para el modelo activo cuando el modelo seleccionado admite control de esfuerzo. Con un nivel, aplica el valor solicitado si es valido para el modelo. Sin un nivel, abre un selector interactivo en el REPL, o imprime el esfuerzo actual en contextos no interactivos. | | `/exit` | Sale del REPL interactivo. Alias: `/quit`, `/q`. | | `/help` | Muestra los comandos disponibles y los atajos de teclado comunes dentro del REPL. Alias: `/?`. | +| `/memory [\|search \|delete \|help]` | Listar, ver, buscar o eliminar memorias guardadas. La creación de memorias en lenguaje natural sigue a cargo del asistente mediante la herramienta de memoria cuando le pides que recuerde algo. | | `/model [model_name]` | Muestra o cambia el modelo activo. Con `model_name`, cambia directamente a ese modelo para el proveedor activo. Sin argumento, abre un selector interactivo de modelos cuando hay un proveedor configurado, o imprime el modelo actual cuando no hay interfaz de consola disponible. | -| `/resume [conversation id or search term]` | Reanuda una sesion anterior. Con un argumento, IaC Code lo resuelve como un ID de sesion o un prefijo de ID unico. Sin argumento, abre el selector interactivo de sesiones. Las sesiones de otros proyectos imprimen un comando `cd ... && iac-code --resume ` en lugar de intercambiar el proyecto actual. | +| `/rename ` | Nombrar la sesión actual. Los nombres aparecen en el banner de bienvenida, en la sugerencia de salida y en el selector de `/resume`, y pueden usarse con `/resume` o `--resume` cuando identifican una sesión de forma única. | +| `/resume [id-de-sesion\|prefijo-unico-de-id\|nombre-unico-de-sesion]` | Reanudar una sesión anterior. Con un argumento, IaC Code lo resuelve como ID exacto, prefijo único de ID o nombre único de sesión. Sin argumento, abre el selector interactivo de sesiones. Las sesiones de otros proyectos imprimen un comando `cd ... && iac-code --resume ` en lugar de cambiar en caliente el proyecto actual. | +| `/skills` | Abrir el selector de gestión de habilidades. Busca habilidades por nombre o descripción, ordena por nombre/origen/tamaño y activa o desactiva habilidades de usuario o de proyecto. Las habilidades incluidas permanecen bloqueadas y activadas. | +| `/status` | Mostrar el ID de sesión actual, proveedor, modelo, región de Alibaba Cloud, directorio de trabajo, uso registrado de tokens de API, número de turnos y utilización del contexto. | La lista exacta de comandos puede cambiar entre versiones. Usa `/help` o escribe `/` en el REPL para inspeccionar los comandos disponibles en tu version instalada. diff --git a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/interactive-mode.md b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/interactive-mode.md index 02c4f1b..80811f1 100644 --- a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/interactive-mode.md +++ b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/interactive-mode.md @@ -25,6 +25,12 @@ Luego describe lo que quieres construir: Create a VPC, two ECS instances, and a security group that allows SSH from my office IP. ``` +## Comandos + +Escribe `/` para descubrir los comandos slash disponibles. Entre los comandos operativos habituales están `/status` para el estado de la sesión actual, `/skills` para gestionar habilidades, `/memory` para las memorias guardadas, `/rename` para nombrar la sesión activa y `/resume` para cambiar de sesión. + +Escribe `$` para descubrir e invocar solo habilidades. + ## Editar la entrada Usa `Shift+Enter` para insertar una nueva linea sin enviar el prompt. Pulsa `Enter` solo para enviar el prompt completo. diff --git a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/sessions.md b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/sessions.md index 2952b56..65769d5 100644 --- a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/sessions.md +++ b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/sessions.md @@ -1,73 +1,90 @@ --- -title: Sessions -description: Persist and resume conversations across runs. +title: Sesiones +description: Persistir y reanudar conversaciones entre ejecuciones. --- -# Sessions +# Sesiones -IaC Code automatically persists every conversation to disk. You can resume any previous session to continue where you left off. +IaC Code guarda automáticamente cada conversación en disco. Puedes reanudar cualquier sesión anterior para continuar donde la dejaste. -## Resuming Sessions +## Reanudar sesiones -### Interactive: `/resume` +### Interactivo: `/resume` -In the REPL, use the `/resume` command: +En el REPL, usa el comando `/resume`: ```text /resume ``` -This opens an interactive picker showing recent sessions for the current project, with their last prompt as the title. +Esto abre un selector interactivo con las sesiones recientes del proyecto actual. Si la sesión tiene nombre, se muestra como título; de lo contrario se usa el último prompt o, como alternativa, el primero. -To resume a specific session by ID or ID prefix: +Para reanudar una sesión concreta por ID exacto, prefijo único de ID o nombre único de sesión: ```text /resume abc123 ``` -### CLI: `--resume` and `--continue` +### Nombrar sesiones -Resume a specific session from the command line: +Usa `/rename` para dar a la sesión activa un nombre estable y legible: + +```text +/rename deploy-prod +``` + +El nombre se guarda en los metadatos de la sesión. Aparece en el banner de bienvenida al reanudar, en la sugerencia de salida y en el selector de `/resume`. + +Puedes reanudar por nombre cuando identifica una sesión de forma única: + +```text +/resume deploy-prod +iac-code --resume deploy-prod +``` + +### CLI: `--resume` y `--continue` + +Reanuda una sesión concreta desde la línea de comandos por ID exacto, prefijo único de ID o nombre único de sesión: ```bash -iac-code --resume +iac-code --resume ``` -Resume the most recent session: +Reanuda la sesión más reciente: ```bash iac-code --continue ``` -The short flags `-r` and `-c` are also available: +También están disponibles las opciones cortas `-r` y `-c`: ```bash -iac-code -r +iac-code -r iac-code -c ``` -### Cross-project Sessions +### Sesiones de otros proyectos -When a session belongs to a different project directory, IaC Code does not hot-swap the working directory. Instead, it prints the command to resume in the correct context: +Cuando una sesión pertenece a otro directorio de proyecto, IaC Code no cambia el directorio de trabajo en caliente. En su lugar, imprime el comando para reanudarla en el contexto correcto: ```text cd /path/to/other/project && iac-code --resume ``` -This command is also copied to the clipboard when possible. +El comando también se copia al portapapeles cuando es posible. -## Interruption Recovery +## Recuperación ante interrupciones -If a session was interrupted mid-execution (e.g., the process was killed while a tool was running), IaC Code detects the orphaned tool calls on resume and appends synthetic error results. This allows the model to recover gracefully without getting stuck waiting for tool output that will never arrive. +Si una sesión se interrumpió durante la ejecución, por ejemplo porque el proceso se terminó mientras una herramienta estaba en curso, IaC Code detecta las llamadas de herramienta huérfanas al reanudar y agrega resultados de error sintéticos. Esto permite que el modelo se recupere sin quedarse esperando una salida de herramienta que nunca llegará. -## Session Picker +## Selector de sesiones -The `/resume` picker displays: +El selector de `/resume` muestra: -| Column | Description | -|--------|-------------| -| Title | Last user prompt (or first prompt if no metadata) | -| Branch | Git branch at the time of the session | -| Time | Last modification time | +| Columna | Descripción | +|---------|-------------| +| Título | Nombre de sesión si existe; de lo contrario, último o primer prompt del usuario | +| Rama | Rama de Git en el momento de la sesión | +| Hora | Última hora de modificación | -Sessions are sorted by most recent first. You can type to filter by title content. +Las sesiones se ordenan de más reciente a más antigua. Puedes escribir para filtrar por contenido del título. diff --git a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/skills.md b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/skills.md index bafcfd8..0cd3e1d 100644 --- a/website/i18n/es/docusaurus-plugin-content-docs/current/cli/skills.md +++ b/website/i18n/es/docusaurus-plugin-content-docs/current/cli/skills.md @@ -89,6 +89,14 @@ paths: | `agent` | No | `"general-purpose"` | Agent type for fork mode | | `paths` | No | `[]` | Glob patterns for path-based auto-activation | +## Gestionar habilidades + +Ejecuta `/skills` en el REPL interactivo para abrir el selector de gestión de habilidades. El selector muestra las habilidades integradas, de usuario y de proyecto descubiertas, junto con su origen, tamaño y estado de habilitación. Puedes buscar por nombre o descripción, ordenar por nombre/origen/tamaño y activar o desactivar habilidades de usuario o de proyecto. + +Las habilidades deshabilitadas se guardan en `settings.yml` bajo `disabled_skills`. Las habilidades integradas permanecen siempre habilitadas y no se escriben en la lista de deshabilitadas. + +Usa `$` cuando quieras que el autocompletado y la invocación apunten solo a habilidades. Es útil cuando el nombre de una habilidad se solapa con texto normal o cuando quieres evitar los comandos slash integrados. + ## Execution Modes ### Inline (default) @@ -188,3 +196,4 @@ Save this as `~/.iac-code/skills/checklist.md` or `.iac-code/skills/checklist.md - **Bundled skills** are always allowed automatically. - **User/project skills** with no shell commands and no `allowed_tools` are auto-allowed. - **Other skills** prompt for user confirmation on first use. +- **Las habilidades de usuario/proyecto deshabilitadas** se ocultan de los listados visibles para el modelo y de los disparadores automáticos; las llamadas directas a la herramienta `skill` devuelven un error de habilidad deshabilitada. diff --git a/website/i18n/es/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md b/website/i18n/es/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md index 81a64de..8e90cce 100644 --- a/website/i18n/es/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md +++ b/website/i18n/es/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md @@ -7,6 +7,22 @@ description: Configurar credenciales de AccessKey o STS de Alibaba Cloud. Las credenciales de Alibaba Cloud son necesarias para las operaciones que inspeccionan o gestionan recursos en la nube. +## Inicio de sesión OAuth en el navegador + +La ruta de configuración interactiva recomendada es `/auth`: + +```text +/auth +``` + +Elige **Configurar servicio cloud de IaC**, luego **Alibaba Cloud** y después **OAuth Login (Browser)**. IaC Code abre un flujo de autorización en el navegador, espera la devolución de llamada local, intercambia el código de autorización con PKCE y guarda credenciales temporales respaldadas por OAuth en `.cloud-credentials.yml`, dentro del directorio de configuración de IaC Code. + +Durante la configuración puedes elegir el sitio OAuth de China o el internacional. IaC Code guarda el sitio elegido junto con el refresh token para que las actualizaciones posteriores usen el mismo endpoint. + +Las credenciales OAuth se actualizan automáticamente cuando el access token o las credenciales STS están por caducar. Si el refresh token caduca o se revoca, ejecuta `/auth` de nuevo y elige OAuth Login (Browser). + +## Variables de entorno + Variables de entorno soportadas: | Variable | Descripcion | diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/command-line-options.md b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/command-line-options.md index 9b6534a..bbea499 100644 --- a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/command-line-options.md +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/command-line-options.md @@ -16,7 +16,7 @@ Les options de ligne de commande modifient le démarrage d'IaC Code. Utilisez-le | `--output-format ` | Définir le format de sortie pour le mode non interactif. Les valeurs prises en charge sont `text`, `json` et `stream-json`. La valeur par défaut est `text`. | | `--max-turns ` | Limiter le nombre maximum de tours de l'agent en mode non interactif. La valeur par défaut est `100`. | | `-d`, `--debug` | Activer la journalisation de débogage pour l'exécution en cours. En mode interactif, utilisez `/debug` pour inspecter ou modifier la journalisation de débogage après le démarrage. | -| `-r `, `--resume ` | Reprendre une session précédente par ID. Ceci permet de revenir à une conversation connue. | +| `-r `, `--resume ` | Reprendre une session précédente par identifiant exact, préfixe d'identifiant unique ou nom de session unique. Les sessions résolues dans un autre projet affichent une commande `cd ... && iac-code --resume ` au lieu de basculer le projet courant à chaud. | | `-c`, `--continue` | Reprendre la session la plus récente. Ne peut pas être utilisé avec `--resume`. | | `--allowed-tools ` | Modèles de permissions d'outils séparés par des virgules à autoriser, ex. `'bash(git *),write_file'`. | | `--disallowed-tools ` | Modèles de permissions d'outils séparés par des virgules à refuser, ex. `'bash(rm *)'`. | diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/commands.md b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/commands.md index e7edfdb..0a26d40 100644 --- a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/commands.md +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/commands.md @@ -20,7 +20,11 @@ Le texte après le nom de la commande est transmis comme arguments. Dans le tabl | `/effort [level]` | Afficher ou modifier l'effort de réflexion pour le modèle actif lorsque le modèle sélectionné prend en charge le contrôle d'effort. Avec un niveau, il applique la valeur demandée si elle est valide pour le modèle. Sans niveau, il ouvre un sélecteur interactif dans le REPL, ou affiche l'effort actuel dans les contextes non interactifs. | | `/exit` | Quitter le REPL interactif. Alias : `/quit`, `/q`. | | `/help` | Afficher les commandes disponibles et les raccourcis clavier courants dans le REPL. Alias : `/?`. | +| `/memory [\|search \|delete \|help]` | Lister, afficher, rechercher ou supprimer les mémoires enregistrées. La création de mémoires en langage naturel reste gérée par l'assistant via l'outil de mémoire lorsque vous lui demandez de se souvenir de quelque chose. | | `/model [model_name]` | Afficher ou changer le modèle actif. Avec `model_name`, il bascule directement vers ce modèle pour le fournisseur actif. Sans argument, il ouvre un sélecteur de modèle interactif lorsqu'un fournisseur est configuré, ou affiche le modèle actuel lorsqu'aucune interface console n'est disponible. | -| `/resume [conversation id or search term]` | Reprendre une session précédente. Avec un argument, IaC Code le résout comme un identifiant de session ou un préfixe d'identifiant unique. Sans argument, il ouvre le sélecteur de session interactif. Les sessions inter-projets affichent une commande `cd ... && iac-code --resume ` au lieu de basculer le projet actuel à chaud. | +| `/rename ` | Nommer la session actuelle. Les noms apparaissent dans la bannière d'accueil, l'indication de sortie et le sélecteur `/resume`, et peuvent être utilisés avec `/resume` ou `--resume` lorsqu'ils identifient une session de façon unique. | +| `/resume [id-de-session\|préfixe-id-unique\|nom-de-session-unique]` | Reprendre une session précédente. Avec un argument, IaC Code le résout comme identifiant exact, préfixe d'identifiant unique ou nom de session unique. Sans argument, il ouvre le sélecteur de session interactif. Les sessions inter-projets affichent une commande `cd ... && iac-code --resume ` au lieu de basculer le projet courant à chaud. | +| `/skills` | Ouvrir le sélecteur de gestion des compétences. Recherchez par nom ou description, triez par nom/source/taille et activez ou désactivez les compétences utilisateur ou projet. Les compétences intégrées restent verrouillées et activées. | +| `/status` | Afficher l'ID de session actuel, le fournisseur, le modèle, la région Alibaba Cloud, le répertoire de travail, l'utilisation enregistrée des tokens d'API, le nombre de tours et l'utilisation du contexte. | La liste exacte des commandes peut varier entre les versions. Utilisez `/help` ou tapez `/` dans le REPL pour inspecter les commandes disponibles dans votre version installée. diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/interactive-mode.md b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/interactive-mode.md index c8f39a2..139a0c7 100644 --- a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/interactive-mode.md +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/interactive-mode.md @@ -25,6 +25,12 @@ Puis décrivez ce que vous souhaitez construire : Create a VPC, two ECS instances, and a security group that allows SSH from my office IP. ``` +## Commandes + +Tapez `/` pour découvrir les commandes slash disponibles. Les commandes opérationnelles courantes incluent `/status` pour l'état de la session actuelle, `/skills` pour la gestion des compétences, `/memory` pour les mémoires enregistrées, `/rename` pour nommer la session active et `/resume` pour changer de session. + +Tapez `$` pour découvrir et invoquer uniquement des compétences. + ## Modifier la saisie Utilisez `Shift+Enter` pour insérer une nouvelle ligne sans envoyer le prompt. Appuyez sur `Enter` seul pour envoyer le prompt complet. diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/sessions.md b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/sessions.md index 2952b56..e587eb1 100644 --- a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/sessions.md +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/sessions.md @@ -1,73 +1,90 @@ --- title: Sessions -description: Persist and resume conversations across runs. +description: Conserver et reprendre les conversations entre les exécutions. --- # Sessions -IaC Code automatically persists every conversation to disk. You can resume any previous session to continue where you left off. +IaC Code enregistre automatiquement chaque conversation sur disque. Vous pouvez reprendre n'importe quelle session précédente pour continuer là où vous vous étiez arrêté. -## Resuming Sessions +## Reprendre des sessions -### Interactive: `/resume` +### Interactif : `/resume` -In the REPL, use the `/resume` command: +Dans le REPL, utilisez la commande `/resume` : ```text /resume ``` -This opens an interactive picker showing recent sessions for the current project, with their last prompt as the title. +Cela ouvre un sélecteur interactif qui affiche les sessions récentes du projet courant. Si un nom de session est défini, il sert de titre ; sinon le dernier prompt, ou à défaut le premier prompt, est utilisé. -To resume a specific session by ID or ID prefix: +Pour reprendre une session précise par identifiant exact, préfixe d'identifiant unique ou nom de session unique : ```text /resume abc123 ``` -### CLI: `--resume` and `--continue` +### Nommer les sessions -Resume a specific session from the command line: +Utilisez `/rename` pour donner à la session active un nom stable et lisible : + +```text +/rename deploy-prod +``` + +Le nom est stocké dans les métadonnées de session. Il apparaît dans la bannière d'accueil lors de la reprise, dans l'indication de sortie et dans le sélecteur `/resume`. + +Vous pouvez reprendre par nom lorsqu'il identifie une session de façon unique : + +```text +/resume deploy-prod +iac-code --resume deploy-prod +``` + +### CLI : `--resume` et `--continue` + +Reprendre une session précise depuis la ligne de commande par identifiant exact, préfixe d'identifiant unique ou nom de session unique : ```bash -iac-code --resume +iac-code --resume ``` -Resume the most recent session: +Reprendre la session la plus récente : ```bash iac-code --continue ``` -The short flags `-r` and `-c` are also available: +Les options courtes `-r` et `-c` sont également disponibles : ```bash -iac-code -r +iac-code -r iac-code -c ``` -### Cross-project Sessions +### Sessions inter-projets -When a session belongs to a different project directory, IaC Code does not hot-swap the working directory. Instead, it prints the command to resume in the correct context: +Lorsqu'une session appartient à un autre répertoire de projet, IaC Code ne change pas le répertoire de travail à chaud. Il affiche plutôt la commande permettant de reprendre dans le bon contexte : ```text cd /path/to/other/project && iac-code --resume ``` -This command is also copied to the clipboard when possible. +Cette commande est aussi copiée dans le presse-papiers lorsque c'est possible. -## Interruption Recovery +## Récupération après interruption -If a session was interrupted mid-execution (e.g., the process was killed while a tool was running), IaC Code detects the orphaned tool calls on resume and appends synthetic error results. This allows the model to recover gracefully without getting stuck waiting for tool output that will never arrive. +Si une session a été interrompue pendant l'exécution, par exemple parce que le processus a été tué pendant qu'un outil tournait, IaC Code détecte les appels d'outil orphelins à la reprise et ajoute des résultats d'erreur synthétiques. Le modèle peut ainsi se rétablir proprement sans rester bloqué en attendant une sortie d'outil qui n'arrivera jamais. -## Session Picker +## Sélecteur de sessions -The `/resume` picker displays: +Le sélecteur `/resume` affiche : -| Column | Description | -|--------|-------------| -| Title | Last user prompt (or first prompt if no metadata) | -| Branch | Git branch at the time of the session | -| Time | Last modification time | +| Colonne | Description | +|---------|-------------| +| Titre | Nom de session s'il existe ; sinon dernier ou premier prompt utilisateur | +| Branche | Branche Git au moment de la session | +| Heure | Dernière modification | -Sessions are sorted by most recent first. You can type to filter by title content. +Les sessions sont triées de la plus récente à la plus ancienne. Vous pouvez taper du texte pour filtrer par contenu du titre. diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/skills.md b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/skills.md index bafcfd8..7a07e16 100644 --- a/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/skills.md +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/cli/skills.md @@ -89,6 +89,14 @@ paths: | `agent` | No | `"general-purpose"` | Agent type for fork mode | | `paths` | No | `[]` | Glob patterns for path-based auto-activation | +## Gérer les compétences + +Exécutez `/skills` dans le REPL interactif pour ouvrir le sélecteur de gestion des compétences. Le sélecteur affiche les compétences intégrées, utilisateur et projet détectées, avec leur source, leur taille et leur état d'activation. Vous pouvez rechercher par nom ou description, trier par nom/source/taille, et activer ou désactiver les compétences utilisateur ou projet. + +Les compétences désactivées sont enregistrées dans `settings.yml` sous `disabled_skills`. Les compétences intégrées restent verrouillées comme activées et ne sont pas écrites dans la liste des désactivations. + +Utilisez `$` lorsque vous voulez limiter l'autocomplétion et l'appel aux compétences uniquement. C'est utile lorsqu'un nom de compétence recoupe du texte ordinaire ou lorsque vous voulez éviter les commandes slash intégrées. + ## Execution Modes ### Inline (default) @@ -188,3 +196,4 @@ Save this as `~/.iac-code/skills/checklist.md` or `.iac-code/skills/checklist.md - **Bundled skills** are always allowed automatically. - **User/project skills** with no shell commands and no `allowed_tools` are auto-allowed. - **Other skills** prompt for user confirmation on first use. +- **Les compétences utilisateur/projet désactivées** sont masquées des listes visibles par le modèle et des déclencheurs automatiques ; les appels directs à l'outil `skill` renvoient une erreur de compétence désactivée. diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md b/website/i18n/fr/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md index 926332a..18a573d 100644 --- a/website/i18n/fr/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md @@ -7,6 +7,22 @@ description: Configurer les identifiants AccessKey ou STS d'Alibaba Cloud. Les identifiants Alibaba Cloud sont requis pour les opérations qui inspectent ou gèrent des ressources cloud. +## Connexion OAuth dans le navigateur + +Le chemin de configuration interactive recommandé est `/auth` : + +```text +/auth +``` + +Choisissez **Configurer le service cloud IaC**, puis **Alibaba Cloud**, puis **OAuth Login (Browser)**. IaC Code ouvre un flux d'autorisation dans le navigateur, attend le callback local, échange le code d'autorisation avec PKCE et enregistre des identifiants temporaires adossés à OAuth dans `.cloud-credentials.yml`, dans le répertoire de configuration d'IaC Code. + +Pendant la configuration, vous pouvez choisir le site OAuth Chine ou international. IaC Code enregistre le site choisi avec le refresh token afin que les actualisations ultérieures utilisent le même endpoint. + +Les identifiants OAuth sont actualisés automatiquement lorsque l'access token ou les identifiants STS arrivent bientôt à expiration. Si le refresh token expire ou est révoqué, exécutez de nouveau `/auth` et choisissez OAuth Login (Browser). + +## Variables d'environnement + Variables d'environnement prises en charge : | Variable | Description | diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/command-line-options.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/command-line-options.md index e07fb2a..a41b377 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/command-line-options.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/command-line-options.md @@ -16,7 +16,7 @@ description: IaC Code の起動オプションとワンショット実行パラ | `--output-format ` | 非対話モードの出力形式を設定します。サポートされる値は `text`、`json`、`stream-json` です。デフォルトは `text` です。 | | `--max-turns ` | 非対話モードでのエージェントの最大ターン数を制限します。デフォルトは `100` です。 | | `-d`, `--debug` | 今回の実行でデバッグログを有効にします。対話モードでは、起動後に `/debug` を使用してデバッグログを確認または変更できます。 | -| `-r `, `--resume ` | ID でセッションを再開します。既知の会話に戻るために使用します。 | +| `-r <セッションIDまたは名前>`, `--resume <セッションIDまたは名前>` | 正確なセッション ID、一意な ID プレフィックス、または一意なセッション名で以前のセッションを再開します。別プロジェクトとして解決されたセッションは、現在のプロジェクトをその場で切り替えず、`cd ... && iac-code --resume ` コマンドを表示します。 | | `-c`, `--continue` | 最新のセッションを再開します。`--resume` と同時に使用できません。 | | `--allowed-tools ` | 許可するツール権限パターンをカンマ区切りで指定します。例:`'bash(git *),write_file'`。 | | `--disallowed-tools ` | 拒否するツール権限パターンをカンマ区切りで指定します。例:`'bash(rm *)'`。 | diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/commands.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/commands.md index 6b3bcf0..03c7e68 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/commands.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/commands.md @@ -20,7 +20,11 @@ description: 組み込み対話コマンドの完全リファレンス。 | `/effort [level]` | 選択したモデルがエフォート制御をサポートしている場合、アクティブモデルの思考エフォートを表示または変更します。レベルを指定すると、モデルに対して有効な値であれば適用します。レベルなしでは REPL で対話ピッカーを開くか、非対話コンテキストでは現在のエフォートを表示します。 | | `/exit` | 対話 REPL を終了します。エイリアス:`/quit`、`/q`。 | | `/help` | REPL 内で利用可能なコマンドと一般的なキーボードショートカットを表示します。エイリアス:`/?`。 | +| `/memory [<名前>\|search <クエリ>\|delete <名前>\|help]` | 保存済みメモリの一覧表示、表示、検索、削除を行います。自然言語でのメモリ作成は、何かを覚えるよう依頼したときに、引き続きアシスタントがメモリツールを通じて処理します。 | | `/model [model_name]` | アクティブなモデルを表示または切り替えます。`model_name` を指定すると、アクティブプロバイダーのそのモデルに直接切り替えます。引数なしでは、プロバイダーが設定されている場合は対話モデルピッカーを開き、コンソール UI が利用できない場合は現在のモデルを表示します。 | -| `/resume [conversation id or search term]` | 前のセッションを再開します。引数を指定すると、セッション ID またはユニーク ID プレフィックスとして解決します。引数なしでは対話セッションピッカーを開きます。プロジェクト間のセッションでは、現在のプロジェクトをホットスワップする代わりに `cd ... && iac-code --resume ` コマンドを表示します。 | +| `/rename <名前>` | 現在のセッションに名前を付けます。名前はウェルカムバナー、終了時のヒント、`/resume` ピッカーに表示され、一意にセッションを識別できる場合は `/resume` または `--resume` で使用できます。 | +| `/resume [セッションID\|一意なIDプレフィックス\|一意なセッション名]` | 以前のセッションを再開します。引数を指定すると、IaC Code は正確なセッション ID、一意な ID プレフィックス、または一意なセッション名として解決します。引数なしでは対話セッションピッカーを開きます。プロジェクト間のセッションでは、現在のプロジェクトをその場で切り替えず、`cd ... && iac-code --resume ` コマンドを表示します。 | +| `/skills` | スキル管理ピッカーを開きます。名前や説明でスキルを検索し、名前/ソース/サイズで並べ替え、ユーザーまたはプロジェクトのスキルを有効化/無効化できます。バンドル済みスキルは有効なままロックされます。 | +| `/status` | 現在のセッション ID、プロバイダー、モデル、Alibaba Cloud リージョン、作業ディレクトリ、記録された API トークン使用量、ターン数、コンテキスト使用率を表示します。 | 正確なコマンドリストはリリースによって変わる可能性があります。インストールされたバージョンで利用可能なコマンドを確認するには `/help` を使用するか、REPL で `/` を入力してください。 diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/interactive-mode.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/interactive-mode.md index 81031eb..c3a7837 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/interactive-mode.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/interactive-mode.md @@ -25,6 +25,12 @@ iac-code Create a VPC, two ECS instances, and a security group that allows SSH from my office IP. ``` +## コマンド + +`/` を入力すると、利用可能なスラッシュコマンドを確認できます。よく使う運用コマンドには、現在のセッション状態を表示する `/status`、スキル管理の `/skills`、保存済みメモリの `/memory`、アクティブなセッションに名前を付ける `/rename`、セッションを切り替える `/resume` があります。 + +`$` を入力すると、スキルだけを検索して呼び出せます。 + ## 入力の編集 `Shift+Enter` を使うと、プロンプトを送信せずに改行を挿入できます。完全なプロンプトを送信するには、通常の `Enter` を押します。 diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/sessions.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/sessions.md index 2952b56..ae8e0c3 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/sessions.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/sessions.md @@ -1,73 +1,90 @@ --- -title: Sessions -description: Persist and resume conversations across runs. +title: セッション +description: 実行をまたいで会話を保存し、再開します。 --- -# Sessions +# セッション -IaC Code automatically persists every conversation to disk. You can resume any previous session to continue where you left off. +IaC Code はすべての会話を自動的にディスクへ保存します。以前のセッションを再開して、中断したところから作業を続けられます。 -## Resuming Sessions +## セッションの再開 -### Interactive: `/resume` +### 対話式:`/resume` -In the REPL, use the `/resume` command: +REPL では `/resume` コマンドを使用します: ```text /resume ``` -This opens an interactive picker showing recent sessions for the current project, with their last prompt as the title. +これにより、現在のプロジェクトの最近のセッションを表示する対話ピッカーが開きます。セッション名が設定されている場合はそれをタイトルとして表示し、未設定の場合は最後のプロンプト、または最初のプロンプトを代替として表示します。 -To resume a specific session by ID or ID prefix: +正確なセッション ID、一意な ID プレフィックス、または一意なセッション名で特定のセッションを再開できます: ```text /resume abc123 ``` -### CLI: `--resume` and `--continue` +### セッションに名前を付ける -Resume a specific session from the command line: +`/rename` を使うと、アクティブなセッションに安定した読みやすい名前を付けられます: + +```text +/rename deploy-prod +``` + +名前はセッションメタデータに保存されます。再開時のウェルカムバナー、終了時のヒント、`/resume` ピッカーに表示されます。 + +名前がセッションを一意に識別する場合は、その名前で再開できます: + +```text +/resume deploy-prod +iac-code --resume deploy-prod +``` + +### CLI:`--resume` と `--continue` + +コマンドラインから、正確なセッション ID、一意な ID プレフィックス、または一意なセッション名で特定のセッションを再開できます: ```bash -iac-code --resume +iac-code --resume <セッションIDまたは名前> ``` -Resume the most recent session: +最新のセッションを再開します: ```bash iac-code --continue ``` -The short flags `-r` and `-c` are also available: +短いオプション `-r` と `-c` も利用できます: ```bash -iac-code -r +iac-code -r <セッションIDまたは名前> iac-code -c ``` -### Cross-project Sessions +### プロジェクト間セッション -When a session belongs to a different project directory, IaC Code does not hot-swap the working directory. Instead, it prints the command to resume in the correct context: +セッションが別のプロジェクトディレクトリに属している場合、IaC Code は作業ディレクトリをその場で切り替えません。代わりに、正しいコンテキストで再開するためのコマンドを表示します: ```text cd /path/to/other/project && iac-code --resume ``` -This command is also copied to the clipboard when possible. +可能な場合、このコマンドはクリップボードにもコピーされます。 -## Interruption Recovery +## 中断からの復旧 -If a session was interrupted mid-execution (e.g., the process was killed while a tool was running), IaC Code detects the orphaned tool calls on resume and appends synthetic error results. This allows the model to recover gracefully without getting stuck waiting for tool output that will never arrive. +ツール実行中にプロセスが終了した場合など、セッションが実行途中で中断された場合、IaC Code は再開時に孤立したツール呼び出しを検出し、合成されたエラー結果を追加します。これにより、モデルは届くことのないツール出力を待ち続けずに復旧できます。 -## Session Picker +## セッションピッカー -The `/resume` picker displays: +`/resume` ピッカーには次の情報が表示されます: -| Column | Description | -|--------|-------------| -| Title | Last user prompt (or first prompt if no metadata) | -| Branch | Git branch at the time of the session | -| Time | Last modification time | +| 列 | 説明 | +|----|------| +| タイトル | 設定済みのセッション名、または最後/最初のユーザープロンプト | +| ブランチ | セッション時点の Git ブランチ | +| 時刻 | 最終更新時刻 | -Sessions are sorted by most recent first. You can type to filter by title content. +セッションは新しい順に並びます。タイトル内容で絞り込むために文字を入力できます。 diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/skills.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/skills.md index bafcfd8..23dcbb1 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/skills.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/cli/skills.md @@ -89,6 +89,14 @@ paths: | `agent` | No | `"general-purpose"` | Agent type for fork mode | | `paths` | No | `[]` | Glob patterns for path-based auto-activation | +## スキルの管理 + +インタラクティブ REPL で `/skills` を実行すると、スキル管理ピッカーを開けます。ピッカーには検出された組み込みスキル、ユーザースキル、プロジェクトスキルが表示され、ソース、サイズ、有効状態を確認できます。名前または説明で検索し、名前/ソース/サイズで並べ替え、ユーザースキルまたはプロジェクトスキルを有効化・無効化できます。 + +無効化されたスキルは `settings.yml` の `disabled_skills` に保存されます。組み込みスキルは常に有効に固定され、無効化リストには書き込まれません。 + +オートコンプリートと呼び出し対象をスキルだけに絞りたい場合は `$` を使用します。スキル名が通常のテキストと重なる場合や、組み込み Slash コマンドを避けたい場合に便利です。 + ## Execution Modes ### Inline (default) @@ -188,3 +196,4 @@ Save this as `~/.iac-code/skills/checklist.md` or `.iac-code/skills/checklist.md - **Bundled skills** are always allowed automatically. - **User/project skills** with no shell commands and no `allowed_tools` are auto-allowed. - **Other skills** prompt for user confirmation on first use. +- **無効化されたユーザー/プロジェクトスキル**は、モデルから見えるスキル一覧と自動トリガーから非表示になり、直接の `skill` ツール呼び出しは無効化スキルのエラーを返します。 diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md index 3c92909..703a06d 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md @@ -7,6 +7,22 @@ description: Alibaba Cloud の AccessKey または STS 認証情報の設定。 Alibaba Cloud の認証情報は、クラウドリソースの検査や管理を行う操作に必要です。 +## OAuth ブラウザログイン + +推奨される対話型セットアップ手順は `/auth` です。 + +```text +/auth +``` + +**IaC クラウドサービスを設定**、**Alibaba Cloud**、**OAuth Login (Browser)** の順に選択します。IaC Code はブラウザの認可フローを開き、ローカル callback を待ち受け、PKCE で認可コードを交換して、OAuth に基づく一時認証情報を IaC Code 設定ディレクトリ内の `.cloud-credentials.yml` に保存します。 + +セットアップ中に、中国または国際版の OAuth サイトを選択できます。IaC Code は選択したサイトを refresh token と一緒に保存し、以降の更新で同じ endpoint を使用します。 + +access token または STS 認証情報の有効期限が近づくと、OAuth 認証情報は自動的に更新されます。refresh token の有効期限が切れた場合、または取り消された場合は、もう一度 `/auth` を実行して OAuth Login (Browser) を選択してください。 + +## 環境変数 + サポートされる環境変数: | 変数 | 説明 | diff --git a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/command-line-options.md b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/command-line-options.md index c2f5a53..745850f 100644 --- a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/command-line-options.md +++ b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/command-line-options.md @@ -16,7 +16,7 @@ As opções de linha de comando alteram como o IaC Code é iniciado. Use-as ante | `--output-format ` | Definir o formato de saída para o modo não interativo. Os valores suportados são `text`, `json` e `stream-json`. O padrão é `text`. | | `--max-turns ` | Limitar o número máximo de turnos do agente no modo não interativo. O padrão é `100`. | | `-d`, `--debug` | Ativar o registro de depuração para a execução atual. No modo interativo, use `/debug` para inspecionar ou alterar o registro de depuração após a inicialização. | -| `-r `, `--resume ` | Retomar uma sessão anterior por ID. Para retornar a uma conversa conhecida. | +| `-r `, `--resume ` | Retomar uma sessão anterior por ID exato, prefixo único de ID ou nome único de sessão. Sessões resolvidas em outro projeto imprimem um comando `cd ... && iac-code --resume ` em vez de trocar o projeto atual em tempo real. | | `-c`, `--continue` | Retomar a sessão mais recente. Não pode ser usado junto com `--resume`. | | `--allowed-tools ` | Padrões de permissão de ferramentas separados por vírgulas para permitir, ex. `'bash(git *),write_file'`. | | `--disallowed-tools ` | Padrões de permissão de ferramentas separados por vírgulas para negar, ex. `'bash(rm *)'`. | diff --git a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/commands.md b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/commands.md index c30feba..33d9541 100644 --- a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/commands.md +++ b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/commands.md @@ -20,7 +20,11 @@ O texto apos o nome do comando e passado como argumentos. Na tabela abaixo, `\|search \|delete \|help]` | Listar, ver, pesquisar ou excluir memórias salvas. A criação de memórias em linguagem natural continua sendo feita pelo assistente por meio da ferramenta de memória quando você pede que ele se lembre de algo. | | `/model [model_name]` | Mostra ou troca o modelo ativo. Com `model_name`, troca diretamente para esse modelo no provedor ativo. Sem argumento, abre um seletor interativo de modelos quando um provedor esta configurado, ou imprime o modelo atual quando nao ha UI de console disponivel. | -| `/resume [conversation id or search term]` | Retoma uma sessao anterior. Com um argumento, o IaC Code resolve-o como um ID de sessao ou prefixo de ID unico. Sem argumento, abre o seletor interativo de sessoes. Sessoes de outros projetos imprimem um comando `cd ... && iac-code --resume ` em vez de trocar o projeto atual. | +| `/rename ` | Nomear a sessão atual. Os nomes aparecem no banner de boas-vindas, na dica de saída e no seletor de `/resume`, e podem ser usados com `/resume` ou `--resume` quando identificam uma sessão de forma única. | +| `/resume [id-da-sessao\|prefixo-unico-de-id\|nome-unico-da-sessao]` | Retomar uma sessão anterior. Com um argumento, o IaC Code resolve-o como ID exato, prefixo único de ID ou nome único de sessão. Sem argumento, abre o seletor interativo de sessões. Sessões de outros projetos imprimem um comando `cd ... && iac-code --resume ` em vez de trocar o projeto atual em tempo real. | +| `/skills` | Abrir o seletor de gerenciamento de habilidades. Pesquise por nome ou descrição, ordene por nome/origem/tamanho e ative ou desative habilidades de usuário ou de projeto. Habilidades integradas permanecem bloqueadas e ativadas. | +| `/status` | Mostrar o ID da sessão atual, provedor, modelo, região da Alibaba Cloud, diretório de trabalho, uso registrado de tokens de API, contagem de turnos e utilização do contexto. | A lista exata de comandos pode mudar entre versoes. Use `/help` ou digite `/` no REPL para inspecionar os comandos disponiveis na sua versao instalada. diff --git a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/interactive-mode.md b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/interactive-mode.md index a62a058..3d6a544 100644 --- a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/interactive-mode.md +++ b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/interactive-mode.md @@ -25,6 +25,12 @@ Em seguida, descreva o que deseja construir: Create a VPC, two ECS instances, and a security group that allows SSH from my office IP. ``` +## Comandos + +Digite `/` para descobrir os comandos slash disponíveis. Comandos operacionais comuns incluem `/status` para o estado da sessão atual, `/skills` para gerenciamento de habilidades, `/memory` para memórias salvas, `/rename` para nomear a sessão ativa e `/resume` para alternar sessões. + +Digite `$` para descobrir e invocar apenas habilidades. + ## Editar entrada Use `Shift+Enter` para inserir uma nova linha sem enviar o prompt. Pressione `Enter` sozinho para enviar o prompt completo. diff --git a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/sessions.md b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/sessions.md index 2952b56..3be4923 100644 --- a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/sessions.md +++ b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/sessions.md @@ -1,73 +1,90 @@ --- -title: Sessions -description: Persist and resume conversations across runs. +title: Sessões +description: Persistir e retomar conversas entre execuções. --- -# Sessions +# Sessões -IaC Code automatically persists every conversation to disk. You can resume any previous session to continue where you left off. +O IaC Code persiste automaticamente cada conversa em disco. Você pode retomar qualquer sessão anterior para continuar de onde parou. -## Resuming Sessions +## Retomar sessões -### Interactive: `/resume` +### Interativo: `/resume` -In the REPL, use the `/resume` command: +No REPL, use o comando `/resume`: ```text /resume ``` -This opens an interactive picker showing recent sessions for the current project, with their last prompt as the title. +Isso abre um seletor interativo com as sessões recentes do projeto atual. Quando um nome de sessão está definido, ele aparece como título; caso contrário, o último prompt ou, como fallback, o primeiro prompt é usado. -To resume a specific session by ID or ID prefix: +Para retomar uma sessão específica por ID exato, prefixo único de ID ou nome único de sessão: ```text /resume abc123 ``` -### CLI: `--resume` and `--continue` +### Nomear sessões -Resume a specific session from the command line: +Use `/rename` para dar à sessão ativa um nome estável e legível: + +```text +/rename deploy-prod +``` + +O nome é armazenado nos metadados da sessão. Ele aparece no banner de boas-vindas ao retomar, na dica de saída e no seletor de `/resume`. + +Você pode retomar pelo nome quando ele identifica uma sessão de forma única: + +```text +/resume deploy-prod +iac-code --resume deploy-prod +``` + +### CLI: `--resume` e `--continue` + +Retome uma sessão específica pela linha de comando por ID exato, prefixo único de ID ou nome único de sessão: ```bash -iac-code --resume +iac-code --resume ``` -Resume the most recent session: +Retome a sessão mais recente: ```bash iac-code --continue ``` -The short flags `-r` and `-c` are also available: +As opções curtas `-r` e `-c` também estão disponíveis: ```bash -iac-code -r +iac-code -r iac-code -c ``` -### Cross-project Sessions +### Sessões entre projetos -When a session belongs to a different project directory, IaC Code does not hot-swap the working directory. Instead, it prints the command to resume in the correct context: +Quando uma sessão pertence a outro diretório de projeto, o IaC Code não troca o diretório de trabalho em tempo real. Em vez disso, imprime o comando para retomar no contexto correto: ```text cd /path/to/other/project && iac-code --resume ``` -This command is also copied to the clipboard when possible. +Esse comando também é copiado para a área de transferência quando possível. -## Interruption Recovery +## Recuperação de interrupções -If a session was interrupted mid-execution (e.g., the process was killed while a tool was running), IaC Code detects the orphaned tool calls on resume and appends synthetic error results. This allows the model to recover gracefully without getting stuck waiting for tool output that will never arrive. +Se uma sessão foi interrompida durante a execução, por exemplo porque o processo foi encerrado enquanto uma ferramenta estava rodando, o IaC Code detecta as chamadas de ferramenta órfãs ao retomar e adiciona resultados de erro sintéticos. Isso permite que o modelo se recupere sem ficar preso aguardando uma saída de ferramenta que nunca chegará. -## Session Picker +## Seletor de sessões -The `/resume` picker displays: +O seletor de `/resume` mostra: -| Column | Description | -|--------|-------------| -| Title | Last user prompt (or first prompt if no metadata) | -| Branch | Git branch at the time of the session | -| Time | Last modification time | +| Coluna | Descrição | +|--------|-----------| +| Título | Nome da sessão quando definido; caso contrário, último ou primeiro prompt do usuário | +| Branch | Branch Git no momento da sessão | +| Hora | Última modificação | -Sessions are sorted by most recent first. You can type to filter by title content. +As sessões são ordenadas da mais recente para a mais antiga. Você pode digitar para filtrar pelo conteúdo do título. diff --git a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/skills.md b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/skills.md index bafcfd8..6c920d8 100644 --- a/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/skills.md +++ b/website/i18n/pt/docusaurus-plugin-content-docs/current/cli/skills.md @@ -89,6 +89,14 @@ paths: | `agent` | No | `"general-purpose"` | Agent type for fork mode | | `paths` | No | `[]` | Glob patterns for path-based auto-activation | +## Gerenciar habilidades + +Execute `/skills` no REPL interativo para abrir o seletor de gerenciamento de habilidades. O seletor lista as habilidades integradas, de usuário e de projeto descobertas, com origem, tamanho e estado de habilitação. Você pode pesquisar por nome ou descrição, ordenar por nome/origem/tamanho e ativar ou desativar habilidades de usuário ou de projeto. + +As habilidades desabilitadas são salvas em `settings.yml` em `disabled_skills`. As habilidades integradas ficam sempre habilitadas e não são gravadas na lista de desabilitadas. + +Use `$` quando quiser que o autocompletar e a invocação apontem apenas para habilidades. Isso é útil quando o nome de uma habilidade se sobrepõe a texto comum ou quando você quer evitar comandos slash integrados. + ## Execution Modes ### Inline (default) @@ -188,3 +196,4 @@ Save this as `~/.iac-code/skills/checklist.md` or `.iac-code/skills/checklist.md - **Bundled skills** are always allowed automatically. - **User/project skills** with no shell commands and no `allowed_tools` are auto-allowed. - **Other skills** prompt for user confirmation on first use. +- **Habilidades de usuário/projeto desabilitadas** ficam ocultas das listas visíveis ao modelo e dos gatilhos automáticos; chamadas diretas à ferramenta `skill` retornam um erro de habilidade desabilitada. diff --git a/website/i18n/pt/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md b/website/i18n/pt/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md index 0c4e671..a2f6b58 100644 --- a/website/i18n/pt/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md +++ b/website/i18n/pt/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md @@ -7,6 +7,22 @@ description: Configure credenciais AccessKey ou STS da Alibaba Cloud. As credenciais da Alibaba Cloud sao necessarias para operacoes que inspecionam ou gerenciam recursos na nuvem. +## Login OAuth no navegador + +O caminho de configuração interativa recomendado é `/auth`: + +```text +/auth +``` + +Escolha **Configurar serviço de nuvem IaC**, depois **Alibaba Cloud** e então **OAuth Login (Browser)**. O IaC Code abre um fluxo de autorização no navegador, aguarda o callback local, troca o código de autorização com PKCE e salva credenciais temporárias baseadas em OAuth em `.cloud-credentials.yml`, no diretório de configuração do IaC Code. + +Durante a configuração, você pode escolher o site OAuth da China ou o internacional. O IaC Code salva o site escolhido junto com o refresh token para que atualizações futuras usem o mesmo endpoint. + +As credenciais OAuth são atualizadas automaticamente quando o access token ou as credenciais STS estão perto de expirar. Se o refresh token expirar ou for revogado, execute `/auth` novamente e escolha OAuth Login (Browser). + +## Variáveis de ambiente + Variaveis de ambiente suportadas: | Variavel | Descricao | diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/command-line-options.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/command-line-options.md index c1470b6..38575fd 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/command-line-options.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/command-line-options.md @@ -16,7 +16,7 @@ description: IaC Code 启动选项和一次性执行参数参考。 | `--output-format ` | 设置非交互模式的输出格式。支持 `text`、`json` 和 `stream-json`,默认值为 `text`。 | | `--max-turns ` | 限制非交互模式中的最大代理轮次,默认值为 `100`。 | | `-d`, `--debug` | 为本次运行启用调试日志。交互模式启动后,可以使用 `/debug` 查看或调整调试日志。 | -| `-r `, `--resume ` | 按会话 ID 恢复历史会话。适合回到已知的对话。 | +| `-r `, `--resume ` | 按精确会话 ID、唯一 ID 前缀或唯一会话名称恢复历史会话。解析到跨项目会话时,会打印 `cd ... && iac-code --resume ` 命令,而不是直接热切换当前项目。 | | `-c`, `--continue` | 恢复最近一次会话。不能与 `--resume` 同时使用。 | | `--allowed-tools ` | 逗号分隔的工具权限允许模式,例如 `'bash(git *),write_file'`。 | | `--disallowed-tools ` | 逗号分隔的工具权限拒绝模式,例如 `'bash(rm *)'`。 | diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/commands.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/commands.md index 48e49ce..8816306 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/commands.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/commands.md @@ -20,7 +20,11 @@ Slash 命令用于在交互式会话中控制 IaC Code。输入 `/` 可以查看 | `/effort [level]` | 在当前模型支持 effort 控制时,查看或切换 thinking effort。带 `level` 时,如果该值对当前模型有效就会直接应用;不带参数时,在 REPL 中打开交互式选择器,在无控制台 UI 的场景中显示当前 effort。 | | `/exit` | 退出交互式 REPL。别名:`/quit`、`/q`。 | | `/help` | 在 REPL 中显示可用命令和常用快捷键。别名:`/?`。 | +| `/memory [\|search \|delete \|help]` | 列出、查看、搜索或删除已保存的记忆。当你让助手记住某件事时,自然语言创建记忆仍由助手通过 memory 工具完成。 | | `/model [model_name]` | 查看或切换当前模型。带 `model_name` 时,会直接为当前提供商切换到该模型;不带参数时,如果已配置提供商,会打开交互式模型选择器;在无控制台 UI 的场景中会显示当前模型。 | -| `/resume [conversation id or search term]` | 恢复历史会话。带参数时,IaC Code 会把它解析为会话 ID 或唯一 ID 前缀;不带参数时打开交互式会话选择器。跨项目会话不会直接热切换,而是打印 `cd ... && iac-code --resume ` 命令。 | +| `/rename ` | 为当前会话命名。名称会显示在欢迎横幅、退出提示和 `/resume` 选择器中;当它能唯一标识一个会话时,也可以用于 `/resume` 或 `--resume`。 | +| `/resume [session id\|unique id prefix\|unique session name]` | 恢复历史会话。带参数时,IaC Code 会把它解析为精确会话 ID、唯一 ID 前缀或唯一会话名称;不带参数时打开交互式会话选择器。跨项目会话不会直接热切换,而是打印 `cd ... && iac-code --resume ` 命令。 | +| `/skills` | 打开技能管理选择器。可以按名称或描述搜索技能,按名称/来源/大小排序,并启用或禁用用户技能和项目技能。内置技能始终锁定为启用。 | +| `/status` | 显示当前会话 ID、提供商、模型、阿里云地域、工作目录、已记录的 API token 用量、轮次数和上下文利用率。 | 准确命令列表可能随版本变化。请在 REPL 中使用 `/help` 或输入 `/` 查看当前安装版本支持的命令。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/interactive-mode.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/interactive-mode.md index e6b565a..3b001f4 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/interactive-mode.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/interactive-mode.md @@ -25,6 +25,12 @@ iac-code 创建一个 VPC、两台 ECS 实例,以及一个允许办公 IP 通过 SSH 访问的安全组。 ``` +## 命令 + +输入 `/` 可以发现可用的 Slash 命令。常用运维命令包括:用 `/status` 查看当前会话状态,用 `/skills` 管理技能,用 `/memory` 查看已保存记忆,用 `/rename` 命名当前会话,以及用 `/resume` 切换会话。 + +输入 `$` 只会发现并调用技能。 + ## 编辑输入 使用 `Shift+Enter` 可以插入换行而不发送 prompt。单独按 `Enter` 会提交完整 prompt。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/sessions.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/sessions.md index ca59635..0fb8e4c 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/sessions.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/sessions.md @@ -17,20 +17,37 @@ IaC Code 会自动将每次对话持久化到磁盘。你可以恢复任何历 /resume ``` -这将打开交互式选择器,显示当前项目的最近会话及其最后一条提示词作为标题。 +这会打开交互式选择器,显示当前项目的最近会话。设置了会话名称时会优先用名称作为标题,否则会使用最后一条提示词,或回退到第一条提示词。 -通过 ID 或 ID 前缀恢复特定会话: +通过精确会话 ID、唯一 ID 前缀或唯一会话名称恢复特定会话: ```text /resume abc123 ``` +### 命名会话 + +使用 `/rename` 为当前会话设置一个稳定、易读的名称: + +```text +/rename deploy-prod +``` + +名称会保存在会话元数据中。恢复会话时,它会显示在欢迎横幅、退出提示和 `/resume` 选择器中。 + +当名称能唯一标识一个会话时,可以按名称恢复: + +```text +/resume deploy-prod +iac-code --resume deploy-prod +``` + ### 命令行:`--resume` 和 `--continue` -从命令行恢复特定会话: +从命令行按精确会话 ID、唯一 ID 前缀或唯一会话名称恢复特定会话: ```bash -iac-code --resume +iac-code --resume ``` 恢复最近的会话: @@ -42,7 +59,7 @@ iac-code --continue 也可使用短标志 `-r` 和 `-c`: ```bash -iac-code -r +iac-code -r iac-code -c ``` @@ -66,7 +83,7 @@ cd /path/to/other/project && iac-code --resume | 列 | 说明 | |----|------| -| 标题 | 最后一条用户提示词(如无元数据则为第一条提示词) | +| 标题 | 设置了会话名称时显示名称,否则显示最后一条或第一条用户提示词 | | 分支 | 会话时的 Git 分支 | | 时间 | 最后修改时间 | diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/skills.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/skills.md index c248449..e7e7701 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/skills.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cli/skills.md @@ -89,6 +89,14 @@ paths: | `agent` | 否 | `"general-purpose"` | fork 模式使用的 agent 类型 | | `paths` | 否 | `[]` | 用于路径自动激活的 glob 模式 | +## 管理技能 + +在交互式 REPL 中运行 `/skills` 可以打开技能管理选择器。选择器会列出已发现的内置技能、用户技能和项目技能,并展示来源、大小和启用状态。你可以按名称或描述搜索,按名称/来源/大小排序,也可以启用或禁用用户技能与项目技能。 + +禁用的技能会保存在 `settings.yml` 的 `disabled_skills` 下。内置技能固定为启用状态,不会写入禁用列表。 + +当你希望自动补全和调用目标只限于技能时,可以使用 `$`。如果某个技能名称容易和普通文本混淆,或者你想避开内置 Slash 命令,这会很有用。 + ## 执行模式 ### Inline(默认) @@ -188,3 +196,4 @@ user_invocable: true - **内置技能**始终自动允许。 - 无 Shell 命令且无 `allowed_tools` 的**用户/项目技能**自动允许。 - **其他技能**首次使用时需要用户确认。 +- **已禁用的用户/项目技能**不会出现在模型可见的技能列表和自动触发中,直接调用 `skill` 工具会返回技能已禁用错误。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md index d14bbaf..8e84826 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/configuration/alibaba-cloud-credentials.md @@ -7,6 +7,22 @@ description: 配置阿里云 AccessKey 或 STS 凭证。 需要检查或管理云资源时,必须配置阿里云凭证。 +## OAuth 浏览器登录 + +推荐的交互式配置入口是 `/auth`: + +```text +/auth +``` + +选择 **配置 IaC 云服务**,然后选择 **Alibaba Cloud**,再选择 **OAuth Login (Browser)**。IaC Code 会打开浏览器授权流程,等待本地回调,使用 PKCE 交换授权码,并将基于 OAuth 的临时凭证保存到 IaC Code 配置目录下的 `.cloud-credentials.yml`。 + +配置过程中可以选择中国站或国际站 OAuth。IaC Code 会把所选站点与 refresh token 一起保存,后续刷新会继续使用同一 endpoint。 + +当 access token 或 STS 凭证即将过期时,OAuth 凭证会自动刷新。如果 refresh token 过期或被撤销,请重新运行 `/auth` 并选择 OAuth Login (Browser)。 + +## 环境变量 + 支持的环境变量: | 变量 | 说明 | From f8453966f7fac43b8b5441d83af0727c39ea8052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 13:57:11 +0800 Subject: [PATCH 10/11] test: pin update checker version fixtures --- tests/services/test_update_checker.py | 74 +++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/tests/services/test_update_checker.py b/tests/services/test_update_checker.py index adcf87e..5f56a05 100644 --- a/tests/services/test_update_checker.py +++ b/tests/services/test_update_checker.py @@ -392,7 +392,13 @@ def fail_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fail_run) - state = check_for_updates_once(path=path, http_client=http_client, now=1000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=1000.0, + python_executable="/python", + ) assert state.pending is not None assert state.pending.version == "0.4.0" @@ -433,7 +439,13 @@ def fake_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fake_run) - state = check_for_updates_once(path=path, http_client=http_client, now=1000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=1000.0, + python_executable="/python", + ) assert state.pending is not None assert state.pending.version == "0.3.2" @@ -465,7 +477,13 @@ def fake_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fake_run) - state = check_for_updates_once(path=path, http_client=http_client, now=1000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=1000.0, + python_executable="/python", + ) assert state.pending is not None assert state.pending.version == "100.0.0" @@ -502,7 +520,13 @@ def fake_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fake_run) - state = check_for_updates_once(path=path, http_client=http_client, now=1000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=1000.0, + python_executable="/python", + ) assert state.pending is None assert state.last_successful_check_at is None @@ -520,6 +544,7 @@ def failed_run(*args, **kwargs): check_for_updates_once( path=path, + current_version="0.3.0", http_client=first_http_client, now=1000.0, python_executable="/python", @@ -540,6 +565,7 @@ def fail_run(*args, **kwargs): second_state = check_for_updates_once( path=path, + current_version="0.3.0", http_client=second_http_client, now=1001.0, python_executable="/python", @@ -600,7 +626,13 @@ def fake_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fake_run) - state = check_for_updates_once(path=path, http_client=http_client, now=1000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=1000.0, + python_executable="/python", + ) assert state.pending is None assert state.last_successful_check_at == 1000.0 @@ -655,7 +687,13 @@ def fail_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fail_run) - state = check_for_updates_once(path=path, http_client=http_client, now=1000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=1000.0, + python_executable="/python", + ) assert state.pending is not None assert state.pending.version == "0.5.0" @@ -837,7 +875,13 @@ def fail_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fail_run) - state = check_for_updates_once(path=path, http_client=http_client, now=1000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=1000.0, + python_executable="/python", + ) assert state.pending is not None assert state.pending.version == "0.3.2" @@ -1105,7 +1149,13 @@ def fake_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fake_run) - state = check_for_updates_once(path=path, http_client=http_client, now=1000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=1000.0, + python_executable="/python", + ) assert state.pending is not None assert state.pending.version == "0.4.0" @@ -1131,7 +1181,13 @@ def fake_run(*args, **kwargs): monkeypatch.setattr(update_checker.subprocess, "run", fake_run) - state = check_for_updates_once(path=path, http_client=http_client, now=8000.0, python_executable="/python") + state = check_for_updates_once( + path=path, + current_version="0.3.0", + http_client=http_client, + now=8000.0, + python_executable="/python", + ) assert state.pending == PendingUpdate(**_pending_update_data()) assert state.last_successful_check_at == 500.0 From 75122c350fe8c9e3569bd70dfa5e0bdfd9409cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=82=E9=A9=AC?= Date: Wed, 3 Jun 2026 14:17:39 +0800 Subject: [PATCH 11/11] test: stabilize windows session and auth coverage --- tests/acp/test_sessions.py | 20 ++++++++++++-------- tests/commands/test_auth_flows.py | 10 ++++++---- tests/ui/test_repl_integration.py | 13 +++++++++---- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/tests/acp/test_sessions.py b/tests/acp/test_sessions.py index fc5ad07..3ac12b8 100644 --- a/tests/acp/test_sessions.py +++ b/tests/acp/test_sessions.py @@ -17,6 +17,7 @@ from iac_code.agent.message import Message, TextBlock from iac_code.services.session_storage import SessionStorage from iac_code.types.stream_events import MessageEndEvent, TextDeltaEvent, Usage +from iac_code.utils.project_paths import format_resume_command class FakeConn: @@ -516,9 +517,10 @@ async def test_resume_active_session_from_other_cwd_raises_hint(monkeypatch: pyt assert isinstance(exc_info.value.data, dict) assert exc_info.value.data["cwd"] == "/source project;unsafe" - assert exc_info.value.data["hint"] == "cd '/source project;unsafe' && iac-code --resume test-session" + expected_hint = format_resume_command("/source project;unsafe", "test-session") + assert exc_info.value.data["hint"] == expected_hint assert sid in str(exc_info.value) - assert "cd '/source project;unsafe' && iac-code --resume test-session" in str(exc_info.value) + assert expected_hint in str(exc_info.value) @pytest.mark.asyncio @@ -547,8 +549,9 @@ async def test_resume_resolved_name_rejects_active_session_from_other_cwd( assert isinstance(exc_info.value.data, dict) assert exc_info.value.data["cwd"] == "/other project;unsafe" - assert exc_info.value.data["hint"] == "cd '/other project;unsafe' && iac-code --resume same-id" - assert "cd '/other project;unsafe' && iac-code --resume same-id" in str(exc_info.value) + expected_hint = format_resume_command("/other project;unsafe", "same-id") + assert exc_info.value.data["hint"] == expected_hint + assert expected_hint in str(exc_info.value) @pytest.mark.asyncio @@ -666,9 +669,10 @@ async def test_resume_session_single_cross_project_match_raises_hint(monkeypatch await server.resume_session(cwd="/tmp", session_id="foreign-deploy") assert isinstance(exc_info.value.data, dict) - assert exc_info.value.data["hint"] == "cd '/other project;unsafe' && iac-code --resume foreign-session-123" + expected_hint = format_resume_command("/other project;unsafe", "foreign-session-123") + assert exc_info.value.data["hint"] == expected_hint assert "foreign-session-123" in str(exc_info.value) - assert "cd '/other project;unsafe' && iac-code --resume foreign-session-123" in str(exc_info.value) + assert expected_hint in str(exc_info.value) @pytest.mark.asyncio @@ -696,8 +700,8 @@ async def test_resume_session_ambiguous_name_raises_candidates(monkeypatch: pyte assert isinstance(exc_info.value.data, dict) candidates = exc_info.value.data["candidates"] commands_by_id = {candidate["session_id"]: candidate["command"] for candidate in candidates} - assert commands_by_id["candidate-a"] == "cd '/project a;bad' && iac-code --resume candidate-a" - assert commands_by_id["candidate-b"] == "cd /project-b && iac-code --resume candidate-b" + assert commands_by_id["candidate-a"] == format_resume_command("/project a;bad", "candidate-a") + assert commands_by_id["candidate-b"] == format_resume_command("/project-b", "candidate-b") @pytest.mark.asyncio diff --git a/tests/commands/test_auth_flows.py b/tests/commands/test_auth_flows.py index dac0701..e6e0371 100644 --- a/tests/commands/test_auth_flows.py +++ b/tests/commands/test_auth_flows.py @@ -631,6 +631,8 @@ def cancel_oauth(site_type, oauth_client=None, cancel_event=None): assert _aliyun_credential_flow() is _BACK def test_oauth_escape_cancel_event_uses_cbreak_mode_to_preserve_output_newlines(self, monkeypatch): + termios = pytest.importorskip("termios") + tty = pytest.importorskip("tty") calls: list[tuple] = [] class FakeStdin: @@ -656,10 +658,10 @@ def fail_setraw(fd): monkeypatch.setattr("iac_code.commands.auth._IS_WIN32", False) monkeypatch.setattr("iac_code.commands.auth.sys.stdin", FakeStdin()) monkeypatch.setattr("iac_code.commands.auth.threading.Thread", FakeThread) - monkeypatch.setattr("termios.tcgetattr", lambda fd: calls.append(("tcgetattr", fd)) or "old-settings") - monkeypatch.setattr("termios.tcsetattr", lambda fd, when, settings: calls.append(("tcsetattr", fd, settings))) - monkeypatch.setattr("tty.setraw", fail_setraw) - monkeypatch.setattr("tty.setcbreak", lambda fd: calls.append(("setcbreak", fd))) + monkeypatch.setattr(termios, "tcgetattr", lambda fd: calls.append(("tcgetattr", fd)) or "old-settings") + monkeypatch.setattr(termios, "tcsetattr", lambda fd, when, settings: calls.append(("tcsetattr", fd, settings))) + monkeypatch.setattr(tty, "setraw", fail_setraw) + monkeypatch.setattr(tty, "setcbreak", lambda fd: calls.append(("setcbreak", fd))) with _oauth_escape_cancel_event(): calls.append(("body",)) diff --git a/tests/ui/test_repl_integration.py b/tests/ui/test_repl_integration.py index fd83901..9733cd2 100644 --- a/tests/ui/test_repl_integration.py +++ b/tests/ui/test_repl_integration.py @@ -13,6 +13,7 @@ from iac_code.services.update_checker import PendingUpdate from iac_code.ui.components.select import SelectLayout +from iac_code.utils.project_paths import format_resume_command @pytest.fixture(autouse=True) @@ -411,9 +412,11 @@ def test_resolve_session_id_continue_cross_project_raises_with_hint(): get_latest_session_anywhere=Mock(return_value=("/elsewhere/repo", "latest-id")) ) - with pytest.raises(ValueError, match=r"cd /elsewhere/repo && iac-code --resume latest-id"): + with pytest.raises(ValueError) as exc_info: repl._resolve_session_id(True) + assert format_resume_command("/elsewhere/repo", "latest-id") in str(exc_info.value) + def test_cross_project_message_uses_windows_resume_command(monkeypatch): import iac_code.utils.project_paths as project_paths @@ -480,10 +483,12 @@ def test_resume_str_cross_project_raises_with_hint(mock_mm, mock_ss, mock_pm): entry=make_session_entry("some-id", "/elsewhere/repo"), ), ), - pytest.raises(ValueError, match=r"cd /elsewhere/repo && iac-code --resume some-id"), + pytest.raises(ValueError) as exc_info, ): InlineREPL(model="test-model", resume_session_id="some-id") + assert format_resume_command("/elsewhere/repo", "some-id") in str(exc_info.value) + def test_resolve_session_id_accepts_current_project_name(): from iac_code.services.session_resolver import ResolutionStatus, SessionResolution @@ -531,8 +536,8 @@ def test_resolve_session_id_ambiguous_name_raises_candidates(): assert "Multiple sessions match" in message assert "abc123" in message assert "def456" in message - assert "cd /repo && iac-code --resume abc123" in message - assert "cd /elsewhere/repo && iac-code --resume def456" in message + assert format_resume_command("/repo", "abc123") in message + assert format_resume_command("/elsewhere/repo", "def456") in message def test_printed_session_name_resume_command_resolves_to_session_id():