iac-code 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. {iac_code-0.3.0 → iac_code-0.3.1}/PKG-INFO +15 -1
  2. {iac_code-0.3.0 → iac_code-0.3.1}/README.md +14 -0
  3. {iac_code-0.3.0 → iac_code-0.3.1}/pyproject.toml +3 -2
  4. iac_code-0.3.1/src/iac_code/__init__.py +2 -0
  5. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/client.py +45 -11
  6. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/base.py +10 -2
  7. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/slash_registry.py +1 -1
  8. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/agent/agent_loop.py +47 -0
  9. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/cli/headless.py +54 -0
  10. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/cli/main.py +37 -4
  11. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/cli/output_formats.py +6 -1
  12. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/clear.py +1 -1
  13. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/registry.py +8 -4
  14. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/config.py +28 -9
  15. iac_code-0.3.1/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  16. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +317 -180
  17. iac_code-0.3.1/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  18. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +319 -180
  19. iac_code-0.3.1/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  20. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +317 -180
  21. iac_code-0.3.1/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  22. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +311 -180
  23. iac_code-0.3.1/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  24. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +317 -180
  25. iac_code-0.3.1/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  26. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +311 -180
  27. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/memory/memory_manager.py +51 -16
  28. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/registry.py +1 -1
  29. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/agent_factory.py +1 -0
  30. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/permissions/loader.py +11 -5
  31. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/session_storage.py +8 -4
  32. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/client.py +1 -1
  33. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/config.py +9 -0
  34. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/fallback.py +4 -2
  35. iac_code-0.3.1/src/iac_code/services/update_checker.py +567 -0
  36. iac_code-0.3.1/src/iac_code/skills/auto_trigger.py +115 -0
  37. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/__init__.py +2 -0
  38. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/SKILL.md +4 -2
  39. iac_code-0.3.1/src/iac_code/skills/bundled/iac_aliyun/__init__.py +20 -0
  40. iac_code-0.3.1/src/iac_code/skills/bundled/iac_aliyun/auto_trigger.py +88 -0
  41. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/discovery.py +42 -26
  42. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/frontmatter.py +4 -0
  43. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/skill_definition.py +4 -0
  44. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/base.py +9 -0
  45. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/aliyun_api.py +5 -0
  46. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/ros_client.py +5 -0
  47. iac_code-0.3.1/src/iac_code/tools/cloud/aliyun/user_agent.py +23 -0
  48. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/edit_file.py +10 -2
  49. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/read_file.py +3 -0
  50. iac_code-0.3.1/src/iac_code/tools/result_storage.py +64 -0
  51. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/write_file.py +7 -1
  52. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/banner.py +37 -0
  53. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/core/input_history.py +36 -6
  54. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/core/prompt_input.py +18 -7
  55. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/core/raw_input.py +58 -0
  56. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/core/raw_input_win.py +51 -0
  57. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/renderer.py +13 -2
  58. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/repl.py +208 -9
  59. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/spinner.py +0 -2
  60. iac_code-0.3.1/src/iac_code/ui/suggestions/skill_provider.py +47 -0
  61. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/suggestions/token_extractor.py +12 -1
  62. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/suggestions/types.py +3 -3
  63. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/file_security.py +14 -0
  64. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/image/store.py +4 -2
  65. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/json_utils.py +38 -0
  66. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/log.py +6 -4
  67. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/project_paths.py +55 -32
  68. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code.egg-info/PKG-INFO +15 -1
  69. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code.egg-info/SOURCES.txt +5 -0
  70. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code.egg-info/requires.txt +3 -2
  71. iac_code-0.3.0/src/iac_code/__init__.py +0 -2
  72. iac_code-0.3.0/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  73. iac_code-0.3.0/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  74. iac_code-0.3.0/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  75. iac_code-0.3.0/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  76. iac_code-0.3.0/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  77. iac_code-0.3.0/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  78. iac_code-0.3.0/src/iac_code/skills/bundled/iac_aliyun/__init__.py +0 -16
  79. iac_code-0.3.0/src/iac_code/tools/result_storage.py +0 -39
  80. {iac_code-0.3.0 → iac_code-0.3.1}/LICENSE +0 -0
  81. {iac_code-0.3.0 → iac_code-0.3.1}/MANIFEST.in +0 -0
  82. {iac_code-0.3.0 → iac_code-0.3.1}/setup.cfg +0 -0
  83. {iac_code-0.3.0 → iac_code-0.3.1}/setup.py +0 -0
  84. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/__init__.py +0 -0
  85. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/agent_card.py +0 -0
  86. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/app.py +0 -0
  87. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/artifacts.py +0 -0
  88. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/events.py +0 -0
  89. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/executor.py +0 -0
  90. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/exposure.py +0 -0
  91. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/metrics.py +0 -0
  92. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/parts.py +0 -0
  93. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/persistence.py +0 -0
  94. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/push.py +0 -0
  95. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/push_queue.py +0 -0
  96. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/push_secrets.py +0 -0
  97. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/push_worker.py +0 -0
  98. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/router.py +0 -0
  99. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/signing.py +0 -0
  100. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/task_store.py +0 -0
  101. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transport.py +0 -0
  102. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/__init__.py +0 -0
  103. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/dispatcher.py +0 -0
  104. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/grpc.py +0 -0
  105. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/grpc_jsonrpc.py +0 -0
  106. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/http.py +0 -0
  107. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/redis_streams.py +0 -0
  108. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/stdio.py +0 -0
  109. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/unix.py +0 -0
  110. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/transports/websocket.py +0 -0
  111. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/a2a/types.py +0 -0
  112. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/__init__.py +0 -0
  113. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/convert.py +0 -0
  114. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/http_sse.py +0 -0
  115. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/mcp.py +0 -0
  116. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/metrics.py +0 -0
  117. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/server.py +0 -0
  118. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/session.py +0 -0
  119. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/state.py +0 -0
  120. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/tools.py +0 -0
  121. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/types.py +0 -0
  122. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/acp/version.py +0 -0
  123. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/agent/__init__.py +0 -0
  124. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/agent/agent_tool.py +0 -0
  125. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/agent/agent_types.py +0 -0
  126. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/agent/message.py +0 -0
  127. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/agent/system_prompt.py +0 -0
  128. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/cli/__init__.py +0 -0
  129. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/cli/install_git_bash.py +0 -0
  130. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/__init__.py +0 -0
  131. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/auth.py +0 -0
  132. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/compact.py +0 -0
  133. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/debug.py +0 -0
  134. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/effort.py +0 -0
  135. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/exit.py +0 -0
  136. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/help.py +0 -0
  137. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/model.py +0 -0
  138. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/resume.py +0 -0
  139. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/commands/tasks.py +0 -0
  140. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/i18n/__init__.py +0 -0
  141. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/memory/__init__.py +0 -0
  142. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/memory/memory_tools.py +0 -0
  143. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/__init__.py +0 -0
  144. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/anthropic_provider.py +0 -0
  145. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/azure_openai_provider.py +0 -0
  146. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/base.py +0 -0
  147. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/dashscope_provider.py +0 -0
  148. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/deepseek_provider.py +0 -0
  149. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/gemini_provider.py +0 -0
  150. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/kimi_provider.py +0 -0
  151. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/lmstudio_provider.py +0 -0
  152. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/manager.py +0 -0
  153. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/minimax_provider.py +0 -0
  154. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/modelscope_provider.py +0 -0
  155. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/ollama_provider.py +0 -0
  156. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/openai_provider.py +0 -0
  157. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/openrouter_provider.py +0 -0
  158. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/retry.py +0 -0
  159. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/siliconflow_provider.py +0 -0
  160. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/stream_watchdog.py +0 -0
  161. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/thinking.py +0 -0
  162. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/volcengine_provider.py +0 -0
  163. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/providers/zhipu_provider.py +0 -0
  164. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/__init__.py +0 -0
  165. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/capabilities/__init__.py +0 -0
  166. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/capabilities/auto_detect.py +0 -0
  167. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/capabilities/multimodal.py +0 -0
  168. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/cloud_credentials.py +0 -0
  169. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/context_manager.py +0 -0
  170. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/permissions/__init__.py +0 -0
  171. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/permissions/pipeline.py +0 -0
  172. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/permissions/storage.py +0 -0
  173. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/providers/__init__.py +0 -0
  174. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/providers/aliyun.py +0 -0
  175. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/qwenpaw_source.py +0 -0
  176. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/session_index.py +0 -0
  177. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/__init__.py +0 -0
  178. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/attributes.py +0 -0
  179. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/constants.py +0 -0
  180. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/content_serializer.py +0 -0
  181. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/events.py +0 -0
  182. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/identity.py +0 -0
  183. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/metrics.py +0 -0
  184. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/names.py +0 -0
  185. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/sanitize.py +0 -0
  186. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/sink.py +0 -0
  187. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/tracing.py +0 -0
  188. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/telemetry/types.py +0 -0
  189. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/token_budget.py +0 -0
  190. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/services/token_counter.py +0 -0
  191. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/__init__.py +0 -0
  192. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/ecs.md +0 -0
  193. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/oss.md +0 -0
  194. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/rds.md +0 -0
  195. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/redis.md +0 -0
  196. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/slb.md +0 -0
  197. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/vpc.md +0 -0
  198. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/ros-template.md +0 -0
  199. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/template-parameters.md +0 -0
  200. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/terraform-template.md +0 -0
  201. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/scripts/tf2ros.py +0 -0
  202. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/bundled/simplify.py +0 -0
  203. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/listing.py +0 -0
  204. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/loader.py +0 -0
  205. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/processor.py +0 -0
  206. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/renderer.py +0 -0
  207. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/skills/skill_tool.py +0 -0
  208. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/state/__init__.py +0 -0
  209. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/state/app_state.py +0 -0
  210. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tasks/__init__.py +0 -0
  211. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tasks/notification_queue.py +0 -0
  212. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tasks/task_state.py +0 -0
  213. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tasks/task_tools.py +0 -0
  214. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/__init__.py +0 -0
  215. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/__init__.py +0 -0
  216. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/bash_tool.py +0 -0
  217. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/command_parser.py +0 -0
  218. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/mode_validation.py +0 -0
  219. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/path_validation.py +0 -0
  220. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/permissions.py +0 -0
  221. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/readonly_commands.py +0 -0
  222. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/rule_matching.py +0 -0
  223. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/bash/safety_checks.py +0 -0
  224. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/__init__.py +0 -0
  225. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/__init__.py +0 -0
  226. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py +0 -0
  227. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/api_hooks.py +0 -0
  228. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/endpoints.yml +0 -0
  229. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/hooks/__init__.py +0 -0
  230. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/hooks/ros_parameters.py +0 -0
  231. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +0 -0
  232. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/ros_stack.py +0 -0
  233. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py +0 -0
  234. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/ros_yaml.py +0 -0
  235. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/base_api.py +0 -0
  236. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/base_stack.py +0 -0
  237. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/registry.py +0 -0
  238. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/cloud/types.py +0 -0
  239. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/glob.py +0 -0
  240. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/grep.py +0 -0
  241. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/list_files.py +0 -0
  242. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/tool_executor.py +0 -0
  243. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/tools/web_fetch.py +0 -0
  244. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/types/__init__.py +0 -0
  245. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/types/permissions.py +0 -0
  246. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/types/skill_source.py +0 -0
  247. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/types/stream_events.py +0 -0
  248. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/__init__.py +0 -0
  249. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/__init__.py +0 -0
  250. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/dialog.py +0 -0
  251. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/divider.py +0 -0
  252. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/fuzzy_picker.py +0 -0
  253. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/progress_bar.py +0 -0
  254. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/search_box.py +0 -0
  255. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/select.py +0 -0
  256. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/status_icon.py +0 -0
  257. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/components/tabs.py +0 -0
  258. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/core/__init__.py +0 -0
  259. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/core/in_place_render.py +0 -0
  260. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/core/key_event.py +0 -0
  261. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/core/screen.py +0 -0
  262. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/dialogs/__init__.py +0 -0
  263. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/dialogs/global_search.py +0 -0
  264. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/dialogs/history_search.py +0 -0
  265. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/dialogs/model_picker.py +0 -0
  266. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/dialogs/quick_open.py +0 -0
  267. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/dialogs/resume_picker.py +0 -0
  268. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/keybindings/__init__.py +0 -0
  269. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/keybindings/manager.py +0 -0
  270. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/suggestions/__init__.py +0 -0
  271. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/suggestions/aggregator.py +0 -0
  272. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/suggestions/command_provider.py +0 -0
  273. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/suggestions/directory_provider.py +0 -0
  274. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/suggestions/file_provider.py +0 -0
  275. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/suggestions/shell_history_provider.py +0 -0
  276. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/ui/transcript_view.py +0 -0
  277. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/__init__.py +0 -0
  278. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/background_housekeeping.py +0 -0
  279. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/cleanup.py +0 -0
  280. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/console.py +0 -0
  281. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/image/__init__.py +0 -0
  282. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/image/clipboard.py +0 -0
  283. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/image/format_detect.py +0 -0
  284. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/image/pasted_content.py +0 -0
  285. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/image/processor.py +0 -0
  286. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/image/resizer.py +0 -0
  287. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/platform.py +0 -0
  288. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/signals.py +0 -0
  289. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/tool_input_parser.py +0 -0
  290. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code/utils/windows_paths.py +0 -0
  291. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code.egg-info/dependency_links.txt +0 -0
  292. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code.egg-info/entry_points.txt +0 -0
  293. {iac_code-0.3.0 → iac_code-0.3.1}/src/iac_code.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iac_code
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Your AI-powered Infrastructure as Code assistant
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.10
@@ -74,6 +74,20 @@ Reading from stdin is also supported:
74
74
  echo "Create an OSS Bucket" | iac-code --prompt -
75
75
  ```
76
76
 
77
+ ## Contributing
78
+
79
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then:
80
+
81
+ ```bash
82
+ make install # install dependencies and pre-commit hooks
83
+ make dev # run in debug mode
84
+ make test # run tests
85
+ make lint # run linters
86
+ make format # format code
87
+ ```
88
+
89
+ See the [Contributing Guide](https://aliyun.github.io/iac-code/getting-started/contributing) for details.
90
+
77
91
  ## Contact Us
78
92
 
79
93
  | [DingTalk](https://qr.dingtalk.com/action/joingroup?code=v1,k1,ubm/77U7qRh/STFZUNBP26X4PNg2z6+uhiPcLGtDNfU=&_dt_no_comment=1&origin=11) | [Discord](https://discord.gg/qECFuFBwF) |
@@ -55,6 +55,20 @@ Reading from stdin is also supported:
55
55
  echo "Create an OSS Bucket" | iac-code --prompt -
56
56
  ```
57
57
 
58
+ ## Contributing
59
+
60
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then:
61
+
62
+ ```bash
63
+ make install # install dependencies and pre-commit hooks
64
+ make dev # run in debug mode
65
+ make test # run tests
66
+ make lint # run linters
67
+ make format # format code
68
+ ```
69
+
70
+ See the [Contributing Guide](https://aliyun.github.io/iac-code/getting-started/contributing) for details.
71
+
58
72
  ## Contact Us
59
73
 
60
74
  | [DingTalk](https://qr.dingtalk.com/action/joingroup?code=v1,k1,ubm/77U7qRh/STFZUNBP26X4PNg2z6+uhiPcLGtDNfU=&_dt_no_comment=1&origin=11) | [Discord](https://discord.gg/qECFuFBwF) |
@@ -21,6 +21,7 @@ dependencies = [
21
21
  "pyperclip>=1.8.0",
22
22
  "openai>=1.50",
23
23
  "httpx>=0.27.0",
24
+ "packaging>=24.0",
24
25
  "tiktoken>=0.7.0",
25
26
  "jsonschema>=4.20",
26
27
  "alibabacloud-ros20190910>=3.0.0",
@@ -32,8 +33,8 @@ dependencies = [
32
33
  "pillow==12.2.0",
33
34
  "cryptography>=42.0",
34
35
  "keyring>=25.0",
35
- "tree-sitter>=0.23",
36
- "tree-sitter-bash>=0.23",
36
+ "tree-sitter>=0.25,<0.26",
37
+ "tree-sitter-bash>=0.25,<0.26",
37
38
  ]
38
39
 
39
40
  [project.optional-dependencies]
@@ -0,0 +1,2 @@
1
+ __version__ = "0.3.1"
2
+ __release_date__ = "2026-06-02"
@@ -7,6 +7,7 @@ from time import monotonic
7
7
  from typing import Any, AsyncIterator
8
8
 
9
9
  import httpx
10
+ from a2a.types import Role
10
11
 
11
12
  from iac_code.a2a.signing import AgentCardSignature, agent_card_signature_jwks_url, verify_agent_card_dict
12
13
  from iac_code.a2a.transport import A2AAuthConfig, A2ATransportBinding, UnsupportedA2ATransportError, headers_for_auth
@@ -31,19 +32,52 @@ class A2AClientResponse:
31
32
  if not isinstance(result, dict):
32
33
  return ""
33
34
  text = result.get("text")
34
- if isinstance(text, str):
35
+ if isinstance(text, str) and text:
35
36
  return text
36
37
  status = result.get("status")
37
- if not isinstance(status, dict):
38
- return ""
39
- message = status.get("message")
40
- if not isinstance(message, dict):
41
- return ""
42
- parts = message.get("parts")
43
- if not isinstance(parts, list) or not parts or not isinstance(parts[0], dict):
44
- return ""
45
- value = parts[0].get("text")
46
- return value if isinstance(value, str) else ""
38
+ if isinstance(status, dict):
39
+ extracted = _extract_parts_text(status.get("message"))
40
+ if extracted:
41
+ return extracted
42
+ task = result.get("task")
43
+ if isinstance(task, dict):
44
+ task_status = task.get("status")
45
+ if isinstance(task_status, dict):
46
+ extracted = _extract_parts_text(task_status.get("message"))
47
+ if extracted:
48
+ return extracted
49
+ history = task.get("history")
50
+ if isinstance(history, list):
51
+ for entry in reversed(history):
52
+ extracted = _extract_agent_entry_text(entry)
53
+ if extracted:
54
+ return extracted
55
+ return ""
56
+
57
+
58
+ _AGENT_ROLE_NAME = Role.Name(Role.ROLE_AGENT)
59
+
60
+
61
+ def _extract_agent_entry_text(entry: Any) -> str:
62
+ if not isinstance(entry, dict) or entry.get("role") != _AGENT_ROLE_NAME:
63
+ return ""
64
+ return _extract_parts_text(entry)
65
+
66
+
67
+ def _extract_parts_text(message: Any) -> str:
68
+ if not isinstance(message, dict):
69
+ return ""
70
+ parts = message.get("parts")
71
+ if not isinstance(parts, list):
72
+ return ""
73
+ pieces: list[str] = []
74
+ for part in parts:
75
+ if not isinstance(part, dict):
76
+ continue
77
+ value = part.get("text")
78
+ if isinstance(value, str):
79
+ pieces.append(value)
80
+ return "".join(pieces)
47
81
 
48
82
 
49
83
  class A2AClient:
@@ -9,7 +9,7 @@ from urllib.parse import urlparse
9
9
  from iac_code.a2a.transport import A2ATransportBinding, UnsupportedA2ATransportError
10
10
  from iac_code.i18n import _
11
11
 
12
- RUNNABLE_TRANSPORTS = frozenset({"http", "stdio", "unix", "websocket", "grpc", "grpc-jsonrpc", "redis-streams"})
12
+ SUPPORTED_TRANSPORTS = frozenset({"http", "stdio", "unix", "websocket", "grpc", "grpc-jsonrpc", "redis-streams"})
13
13
 
14
14
 
15
15
  class A2ATransportConfigError(ValueError):
@@ -159,8 +159,16 @@ def select_binding(bindings: Sequence[A2ATransportBinding]) -> A2ATransportBindi
159
159
  raise UnsupportedA2ATransportError(f"No runnable A2A transport found. Candidate bindings: {names}")
160
160
 
161
161
 
162
+ def validate_transport_supported(transport: str) -> None:
163
+ """Raise ValueError if the transport name is not recognised."""
164
+ if transport not in SUPPORTED_TRANSPORTS:
165
+ supported = ", ".join(sorted(SUPPORTED_TRANSPORTS))
166
+ raise ValueError(f"Unsupported transport '{transport}'. Supported values: {supported}")
167
+
168
+
162
169
  def validate_transport_for_platform(transport: str) -> None:
163
- """Raise RuntimeError if the transport is unavailable on the current platform."""
170
+ """Raise an error if the transport is unsupported or unavailable on the current platform."""
171
+ validate_transport_supported(transport)
164
172
  if transport == "unix" and sys.platform == "win32":
165
173
  raise RuntimeError(
166
174
  _(
@@ -91,7 +91,7 @@ class ACPSlashRegistry:
91
91
  async def _handle_clear(self, agent_loop) -> str:
92
92
  """Clear the agent_loop conversation history."""
93
93
  try:
94
- agent_loop.context_manager.reset()
94
+ agent_loop.reset()
95
95
  except Exception as exc:
96
96
  logger.warning("ACP /clear failed: %s", exc)
97
97
  return _("Clear failed: {error}").format(error=exc)
@@ -32,6 +32,7 @@ from iac_code.types.stream_events import (
32
32
  ToolResultEvent,
33
33
  ToolUseEndEvent,
34
34
  ToolUseStartEvent,
35
+ Usage,
35
36
  )
36
37
 
37
38
 
@@ -69,6 +70,7 @@ class AgentLoop:
69
70
  cwd: str | None = None,
70
71
  permission_context: Any = None, # ToolPermissionContext
71
72
  permission_context_getter: Any = None, # Callable[[], ToolPermissionContext | None]
73
+ auto_trigger_skills: list[Any] | None = None,
72
74
  ) -> None:
73
75
  self._provider_manager = provider_manager
74
76
  self.system_prompt = system_prompt
@@ -79,6 +81,8 @@ class AgentLoop:
79
81
  self._cwd = cwd or os.getcwd()
80
82
  self._permission_context = permission_context
81
83
  self._permission_context_getter = permission_context_getter
84
+ self._auto_trigger_skills = auto_trigger_skills or []
85
+ self._auto_loaded_skills: set[str] = set()
82
86
  self._current_git_branch: str | None = None
83
87
 
84
88
  model_name = ""
@@ -224,6 +228,7 @@ class AgentLoop:
224
228
  # between turns (user runs git checkout via Bash tool), but
225
229
  # is treated as stable within a single in-flight request.
226
230
  self._refresh_git_branch()
231
+ await self._apply_auto_triggers(user_input)
227
232
  self.context_manager.add_user_message(user_input)
228
233
  if self._session_storage:
229
234
  from iac_code.agent.message import Message
@@ -535,6 +540,46 @@ class AgentLoop:
535
540
  self.context_manager.add_raw_message(msg)
536
541
  if result.context_modifier is not None:
537
542
  self._apply_context_modifier(result.context_modifier)
543
+ else:
544
+ yield MessageEndEvent(stop_reason="max_turns", usage=Usage())
545
+
546
+ async def _apply_auto_triggers(self, user_input: str | list[ContentBlock]) -> None:
547
+ if not self._auto_trigger_skills:
548
+ return
549
+ if all(command.name in self._auto_loaded_skills for command in self._auto_trigger_skills):
550
+ return
551
+ prompt_text = self._auto_trigger_text(user_input)
552
+ if not prompt_text:
553
+ return
554
+
555
+ from iac_code.skills.auto_trigger import process_auto_triggered_skills
556
+
557
+ results = await process_auto_triggered_skills(
558
+ prompt_text,
559
+ self._auto_trigger_skills,
560
+ loaded_skill_names=self._auto_loaded_skills,
561
+ context_messages=self.context_manager.get_messages(),
562
+ session_id=self._session_id,
563
+ )
564
+ for result in results:
565
+ for msg in result.new_messages:
566
+ injected = self.context_manager.add_raw_message(msg)
567
+ if self._session_storage:
568
+ self._session_storage.append(
569
+ self._cwd,
570
+ self._session_id,
571
+ injected,
572
+ git_branch=self._current_git_branch,
573
+ )
574
+ if result.context_modifier is not None:
575
+ self._apply_context_modifier(result.context_modifier)
576
+
577
+ @staticmethod
578
+ def _auto_trigger_text(user_input: str | list[ContentBlock]) -> str:
579
+ if isinstance(user_input, str):
580
+ return user_input
581
+ parts = [block.text for block in user_input if isinstance(block, TextBlock)]
582
+ return " ".join(part for part in parts if part).strip()
538
583
 
539
584
  def _apply_context_modifier(self, modifier: Any) -> None:
540
585
  """Apply a context modifier from a ToolResult to the current execution context."""
@@ -642,6 +687,7 @@ class AgentLoop:
642
687
 
643
688
  self._session_id = session_id
644
689
  self._current_git_branch = None
690
+ self._auto_loaded_skills.clear()
645
691
  self.context_manager.reset()
646
692
  if resume_messages:
647
693
  self.context_manager.load_messages(resume_messages)
@@ -663,6 +709,7 @@ class AgentLoop:
663
709
  self._current_git_branch = None
664
710
 
665
711
  def reset(self) -> None:
712
+ self._auto_loaded_skills.clear()
666
713
  self.context_manager.reset()
667
714
 
668
715
  def get_context_usage(self) -> dict:
@@ -24,6 +24,11 @@ from iac_code.types.stream_events import (
24
24
  ErrorEvent,
25
25
  MessageEndEvent,
26
26
  PermissionRequestEvent,
27
+ StackInstancesProgressEvent,
28
+ StackProgressEvent,
29
+ SubAgentToolEvent,
30
+ ToolResultEvent,
31
+ ToolUseStartEvent,
27
32
  )
28
33
  from iac_code.utils.background_housekeeping import start_background_housekeeping
29
34
 
@@ -33,6 +38,47 @@ EXIT_MAX_TURNS = 2
33
38
  __all__ = ["HeadlessRunner", "logger"]
34
39
 
35
40
 
41
+ class _ProgressWriter:
42
+ """Write human-readable headless progress to stderr."""
43
+
44
+ def __init__(self, stream: IO[str]) -> None:
45
+ self._stream = stream
46
+
47
+ def handle(self, event: Any) -> None:
48
+ line: str | None = None
49
+ if isinstance(event, ToolUseStartEvent):
50
+ line = _("Tool started: {}").format(event.name)
51
+ elif isinstance(event, ToolResultEvent):
52
+ if event.is_error:
53
+ line = _("Tool failed: {}").format(event.tool_name)
54
+ else:
55
+ line = _("Tool finished: {}").format(event.tool_name)
56
+ elif isinstance(event, SubAgentToolEvent):
57
+ if event.is_done:
58
+ if event.is_error:
59
+ line = _("Child tool failed: {}").format(event.child_tool_name)
60
+ else:
61
+ line = _("Child tool finished: {}").format(event.child_tool_name)
62
+ else:
63
+ line = _("Child tool started: {}").format(event.child_tool_name)
64
+ elif isinstance(event, StackProgressEvent):
65
+ line = _("Stack {}: {} ({:.1f}%)").format(
66
+ event.stack_name,
67
+ event.status,
68
+ event.progress_percentage,
69
+ )
70
+ elif isinstance(event, StackInstancesProgressEvent):
71
+ line = _("Stack group {}: {} ({}%)").format(
72
+ event.stack_group_name,
73
+ event.status,
74
+ event.progress_percentage,
75
+ )
76
+
77
+ if line is not None:
78
+ self._stream.write(line + "\n")
79
+ self._stream.flush()
80
+
81
+
36
82
  class HeadlessRunner:
37
83
  """Run a single prompt headlessly, auto-approving all permission requests."""
38
84
 
@@ -45,6 +91,8 @@ class HeadlessRunner:
45
91
  cli_allowed_tools: list[str] | None = None,
46
92
  cli_disallowed_tools: list[str] | None = None,
47
93
  cli_permission_mode: str | None = None,
94
+ verbose: bool = False,
95
+ progress_stream: IO[str] | None = None,
48
96
  ) -> None:
49
97
  self._model = model
50
98
  self._output_format = output_format
@@ -53,6 +101,8 @@ class HeadlessRunner:
53
101
  self._cli_allowed_tools = cli_allowed_tools
54
102
  self._cli_disallowed_tools = cli_disallowed_tools
55
103
  self._cli_permission_mode = cli_permission_mode
104
+ self._verbose = verbose
105
+ self._progress_stream = progress_stream or sys.stderr
56
106
 
57
107
  def _print_provider_not_configured(self, exc: Exception) -> None:
58
108
  logger.error("Provider not configured: {}", exc)
@@ -91,6 +141,7 @@ class HeadlessRunner:
91
141
 
92
142
  agent_loop = self._create_agent_loop()
93
143
  writer = create_writer(self._output_format, self._output_stream)
144
+ progress_writer = _ProgressWriter(self._progress_stream) if self._verbose else None
94
145
 
95
146
  has_error = False
96
147
  hit_max_turns = False
@@ -118,6 +169,9 @@ class HeadlessRunner:
118
169
  if isinstance(event, MessageEndEvent) and event.stop_reason == "max_turns":
119
170
  hit_max_turns = True
120
171
 
172
+ if progress_writer is not None:
173
+ progress_writer.handle(event)
174
+
121
175
  writer.handle(event)
122
176
  except ProviderNotConfiguredError as exc:
123
177
  self._print_provider_not_configured(exc)
@@ -84,6 +84,7 @@ def main(
84
84
  output_format: str = typer.Option("text", "--output-format", help=_("Output format: text, json, stream-json")),
85
85
  max_turns: int = typer.Option(100, "--max-turns", help=_("Maximum agent turns in headless mode")),
86
86
  debug: bool = typer.Option(False, "--debug", "-d", help=_("Enable debug logging")),
87
+ verbose: bool = typer.Option(False, "--verbose", help=_("Show headless progress on stderr")),
87
88
  version: bool = typer.Option(False, "--version", "-v", "-V", is_eager=True, help=_("Show version and exit")),
88
89
  resume: str = typer.Option("", "--resume", "-r", help=_("Resume a session by ID")),
89
90
  continue_session: bool = typer.Option(False, "--continue", "-c", help=_("Resume the most recent session")),
@@ -150,6 +151,18 @@ def main(
150
151
  typer.echo(_("Error: --resume and --continue cannot be used together."), err=True)
151
152
  raise typer.Exit(1)
152
153
 
154
+ fmt = None
155
+ if prompt:
156
+ from iac_code.cli.output_formats import OutputFormat
157
+
158
+ normalized_output_format = (output_format or "text").strip().lower()
159
+ try:
160
+ fmt = OutputFormat(normalized_output_format)
161
+ except ValueError as exc:
162
+ valid = ", ".join(item.value for item in OutputFormat)
163
+ typer.echo(_("Invalid --output-format '{}'. Valid values: {}").format(output_format, valid), err=True)
164
+ raise typer.Exit(1) from exc
165
+
153
166
  # Priority: CLI parameter > saved config > default
154
167
  if not model:
155
168
  try:
@@ -159,10 +172,21 @@ def main(
159
172
  raise typer.Exit(1)
160
173
 
161
174
  if prompt:
175
+ assert fmt is not None
176
+
162
177
  # Read from stdin if prompt is "-"
163
178
  if prompt == "-":
164
179
  prompt = sys.stdin.read().strip()
165
180
 
181
+ if permission_mode:
182
+ from iac_code.services.permissions.loader import parse_cli_permission_mode
183
+
184
+ try:
185
+ parse_cli_permission_mode(permission_mode)
186
+ except ValueError as exc:
187
+ typer.echo(str(exc), err=True)
188
+ raise typer.Exit(1) from exc
189
+
166
190
  # Headless mode: generate session_id for logging only
167
191
  session_id = str(uuid.uuid4())
168
192
  setup_logging(session_id=session_id, debug=debug)
@@ -175,7 +199,7 @@ def main(
175
199
  Events.SESSION_STARTED,
176
200
  {
177
201
  "headless": True,
178
- "output_format": output_format or "text",
202
+ "output_format": fmt.value,
179
203
  },
180
204
  )
181
205
  add_metric(Metrics.SESSION_COUNT, 1, {})
@@ -215,9 +239,7 @@ def main(
215
239
  return await coro
216
240
 
217
241
  from iac_code.cli.headless import HeadlessRunner
218
- from iac_code.cli.output_formats import OutputFormat
219
242
 
220
- fmt = OutputFormat(output_format)
221
243
  cli_allowed = [s.strip() for s in allowed_tools.split(",") if s.strip()] if allowed_tools else None
222
244
  cli_disallowed = [s.strip() for s in disallowed_tools.split(",") if s.strip()] if disallowed_tools else None
223
245
  try:
@@ -228,6 +250,7 @@ def main(
228
250
  cli_allowed_tools=cli_allowed,
229
251
  cli_disallowed_tools=cli_disallowed,
230
252
  cli_permission_mode=permission_mode or None,
253
+ verbose=verbose,
231
254
  )
232
255
  exit_code = asyncio.run(_run_with_handler(runner.run(prompt)))
233
256
  except _QwenPawError as exc:
@@ -573,7 +596,11 @@ def a2a(
573
596
  ),
574
597
  ) -> None:
575
598
  """Run iac-code as an A2A 1.0 server."""
576
- config = _load_a2a_config(config_path) if config_path else {}
599
+ try:
600
+ config = _load_a2a_config(config_path) if config_path else {}
601
+ except Exception as exc:
602
+ typer.echo(str(exc), err=True)
603
+ raise typer.Exit(1) from exc
577
604
  host = _a2a_config_value(ctx, config, "host", host)
578
605
  port = _a2a_config_value(ctx, config, "port", port)
579
606
  transport = _a2a_config_value(ctx, config, "transport", transport)
@@ -789,6 +816,8 @@ def a2a_call(
789
816
  route = _a2a_client_route_specs(ctx, config, route)
790
817
  route_name = _a2a_config_value(ctx, config, "route_name", route_name)
791
818
  cwd = _a2a_config_value(ctx, config, "cwd", cwd)
819
+ if cwd in ("", "."):
820
+ cwd = str(Path.cwd())
792
821
  context_id = _a2a_config_value(ctx, config, "context_id", context_id)
793
822
  timeout = _a2a_config_value(ctx, config, "timeout", timeout)
794
823
  stream = _a2a_config_value(ctx, config, "stream", stream)
@@ -809,6 +838,10 @@ def a2a_call(
809
838
  require_card_signature=require_card_signature,
810
839
  )
811
840
  if not url:
841
+ if not route and not route_name:
842
+ raise ValueError(
843
+ "url is required. Provide --url or url in --config, or configure --route/--route-name."
844
+ )
812
845
  from iac_code.a2a.router import A2ARouter
813
846
 
814
847
  selected = A2ARouter([_parse_a2a_route_spec(value) for value in route]).resolve(
@@ -81,12 +81,17 @@ class JsonWriter:
81
81
  entry["result"] = event.result
82
82
  entry["is_error"] = event.is_error
83
83
  elif isinstance(event, MessageEndEvent):
84
- self._usage = {
84
+ usage = {
85
85
  "input_tokens": event.usage.input_tokens,
86
86
  "output_tokens": event.usage.output_tokens,
87
87
  "cache_creation_input_tokens": event.usage.cache_creation_input_tokens,
88
88
  "cache_read_input_tokens": event.usage.cache_read_input_tokens,
89
89
  }
90
+ is_empty_synthetic_max_turns = (
91
+ event.stop_reason == "max_turns" and self._usage is not None and not any(usage.values())
92
+ )
93
+ if not is_empty_synthetic_max_turns:
94
+ self._usage = usage
90
95
  elif isinstance(event, ErrorEvent):
91
96
  self._error = event.error
92
97
 
@@ -12,7 +12,7 @@ async def clear_command(context=None, **kwargs) -> str:
12
12
  if context and hasattr(context, "repl"):
13
13
  agent_loop = getattr(context.repl, "_agent_loop", None)
14
14
  if agent_loop:
15
- agent_loop.context_manager.reset()
15
+ agent_loop.reset()
16
16
  if hasattr(context.repl, "_command_log"):
17
17
  context.repl._command_log.clear()
18
18
 
@@ -240,12 +240,16 @@ class CommandRegistry:
240
240
  return None
241
241
 
242
242
  def is_command(self, text: str) -> bool:
243
- """Check if text is a command"""
244
- return text.startswith("/")
243
+ """Check if text is a command (``/``) or skill (``$``) invocation."""
244
+ return text.startswith(("/", "$"))
245
245
 
246
246
  def parse(self, text: str) -> tuple[str, list[str]]:
247
- """Parse command text, return (command name, argument list)"""
248
- parts = text.lstrip("/").split()
247
+ """Parse command text, return (command name, argument list).
248
+
249
+ Accepts both the ``/`` trigger (commands + skills) and the ``$``
250
+ trigger (skills only).
251
+ """
252
+ parts = text.lstrip("/$").split()
249
253
  name = parts[0] if parts else ""
250
254
  args = parts[1:] if len(parts) > 1 else []
251
255
  return name, args
@@ -15,6 +15,9 @@ from typing import Any
15
15
 
16
16
  import yaml
17
17
 
18
+ from iac_code.i18n import _
19
+ from iac_code.utils.file_security import ensure_private_dir, ensure_private_file
20
+
18
21
  # Default LLM model used when no model is saved in settings
19
22
  DEFAULT_MODEL = "qwen3.7-max"
20
23
 
@@ -47,8 +50,9 @@ def _load_yaml(path: Path) -> dict[str, Any]:
47
50
 
48
51
  def _save_yaml(path: Path, data: dict[str, Any]) -> None:
49
52
  """Write *data* to a YAML file, creating parent directories as needed."""
50
- path.parent.mkdir(parents=True, exist_ok=True)
53
+ ensure_private_dir(path.parent)
51
54
  path.write_text(yaml.dump(data, default_flow_style=False, allow_unicode=True), encoding="utf-8")
55
+ ensure_private_file(path)
52
56
 
53
57
 
54
58
  # ---------------------------------------------------------------------------
@@ -56,15 +60,29 @@ def _save_yaml(path: Path, data: dict[str, Any]) -> None:
56
60
  # ---------------------------------------------------------------------------
57
61
 
58
62
 
63
+ def _normalize_provider_lookup_name(value: str) -> str:
64
+ """Normalize provider display names and keys for user-facing lookup."""
65
+ return value.lower().replace(" ", "").replace("-", "").replace("_", "")
66
+
67
+
59
68
  def _build_provider_name_to_key() -> dict[str, str]:
60
69
  from iac_code.providers.registry import PROVIDER_REGISTRY
61
70
 
62
71
  mapping: dict[str, str] = {}
72
+
73
+ def _add_alias(alias: str, provider_key: str) -> None:
74
+ normalized = _normalize_provider_lookup_name(alias)
75
+ existing = mapping.get(normalized)
76
+ if existing is not None and existing != provider_key:
77
+ raise ValueError(
78
+ f"Ambiguous provider alias {alias!r}: normalized form {normalized!r} "
79
+ f"maps to both {existing!r} and {provider_key!r}"
80
+ )
81
+ mapping[normalized] = provider_key
82
+
63
83
  for desc in PROVIDER_REGISTRY.values():
64
- normalized = desc.name.lower().replace(" ", "").replace("-", "").replace("_", "")
65
- mapping[normalized] = desc.key
66
- key_norm = desc.key.lower().replace("_", "").replace("-", "")
67
- mapping[key_norm] = desc.key
84
+ _add_alias(desc.name, desc.key)
85
+ _add_alias(desc.key, desc.key)
68
86
  return mapping
69
87
 
70
88
 
@@ -139,11 +157,13 @@ def _get_env_overrides() -> dict[str, str | None]:
139
157
  provider_raw = _read("IAC_CODE_PROVIDER")
140
158
  provider_key: str | None = None
141
159
  if provider_raw is not None:
142
- key = _PROVIDER_NAME_TO_KEY.get(provider_raw.lower())
160
+ key = _PROVIDER_NAME_TO_KEY.get(_normalize_provider_lookup_name(provider_raw))
143
161
  if key is None:
144
162
  valid = ", ".join(_PROVIDER_CANONICAL_NAMES)
145
163
  raise ValueError(
146
- f"Invalid IAC_CODE_PROVIDER value: {provider_raw!r}. Valid values (case-insensitive): {valid}"
164
+ _("Invalid IAC_CODE_PROVIDER value: {!r}. Valid values (case-insensitive): {}").format(
165
+ provider_raw, valid
166
+ )
147
167
  )
148
168
  provider_key = key
149
169
 
@@ -248,8 +268,7 @@ def get_config_dir() -> Path:
248
268
  caching).
249
269
  """
250
270
  config_dir = _resolve_config_dir()
251
- config_dir.mkdir(parents=True, exist_ok=True)
252
- return config_dir
271
+ return ensure_private_dir(config_dir)
253
272
 
254
273
 
255
274
  def get_credentials_path() -> Path: