ErisPulse 2.9.0.dev1__tar.gz → 2.9.0.dev2__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 (142) hide show
  1. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/.gitignore +6 -2
  2. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/PKG-INFO +2 -1
  3. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/pyproject.toml +3 -1
  4. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/cli.py +12 -0
  5. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/doctor.py +47 -1
  6. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/init.py +11 -7
  7. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/install.py +10 -6
  8. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/list.py +40 -6
  9. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/run.py +20 -0
  10. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/self_update.py +5 -4
  11. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/upgrade.py +9 -3
  12. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/constants.py +5 -0
  13. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/en.py +13 -0
  14. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/ja.py +13 -0
  15. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/ru.py +13 -0
  16. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +13 -0
  17. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +13 -0
  18. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/config_wizard.py +7 -6
  19. erispulse-2.9.0.dev2/src/ErisPulse/CLI/utils/interactive.py +127 -0
  20. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/package_manager.py +9 -6
  21. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/kv_builder.py +4 -2
  22. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/__init__.py +1 -0
  23. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/adapter.py +67 -0
  24. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/config.py +214 -1
  25. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/constants.py +4 -0
  26. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/en.py +11 -2
  27. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/ja.py +11 -2
  28. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/ru.py +11 -2
  29. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/zh_cn.py +11 -2
  30. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/zh_tw.py +11 -2
  31. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/logger.py +1 -1
  32. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/module.py +42 -3
  33. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/router.py +184 -0
  34. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/module.py +168 -8
  35. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/__init__.py +1 -0
  36. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/sdk.py +27 -2
  37. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/LICENSE +0 -0
  38. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/README.pypi.md +0 -0
  39. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/__init__.py +0 -0
  40. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/base.py +0 -0
  41. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/__init__.py +0 -0
  42. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/config.py +0 -0
  43. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/create.py +0 -0
  44. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/language.py +0 -0
  45. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
  46. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/types.py +0 -0
  47. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
  48. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/version.py +0 -0
  49. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/console.py +0 -0
  50. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/hints.py +0 -0
  51. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
  52. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
  53. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/registry.py +0 -0
  54. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/__init__.py +0 -0
  55. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/display.py +0 -0
  56. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/scaffold_text.py +0 -0
  57. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/__init__.py +0 -0
  58. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/adapter.py +0 -0
  59. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/api_dsl.py +0 -0
  60. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/client.py +0 -0
  61. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/config_schema.py +0 -0
  62. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/converter.py +0 -0
  63. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/errors.py +0 -0
  64. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
  65. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/manager.py +0 -0
  66. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/model.py +0 -0
  67. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/module.py +0 -0
  68. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/router.py +0 -0
  69. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
  70. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
  71. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/sql_base.py +0 -0
  72. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/storage.py +0 -0
  73. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/websocket.py +0 -0
  74. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/__init__.py +0 -0
  75. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/base.py +0 -0
  76. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/command.py +0 -0
  77. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/command_args.py +0 -0
  78. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/conversation.py +0 -0
  79. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/governance.py +0 -0
  80. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/interaction.py +0 -0
  81. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/message.py +0 -0
  82. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/message_builder.py +0 -0
  83. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/meta.py +0 -0
  84. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/notice.py +0 -0
  85. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/overrides.py +0 -0
  86. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/request.py +0 -0
  87. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/session_type.py +0 -0
  88. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/throttle.py +0 -0
  89. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/trace.py +0 -0
  90. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/wrapper.py +0 -0
  91. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/__init__.py +0 -0
  92. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/error.css +0 -0
  93. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/error.html +0 -0
  94. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/root.css +0 -0
  95. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/root.html +0 -0
  96. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/client.py +0 -0
  97. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/di.py +0 -0
  98. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/__init__.py +0 -0
  99. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/constants.py +0 -0
  100. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
  101. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/lifecycle.py +0 -0
  102. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/master.py +0 -0
  103. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/ownership.py +0 -0
  104. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/scope.py +0 -0
  105. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/shadow.py +0 -0
  106. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/storage/__init__.py +0 -0
  107. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/storage/mysql.py +0 -0
  108. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/storage/postgres.py +0 -0
  109. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/storage/sqlite.py +0 -0
  110. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/text_match.py +0 -0
  111. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/transcript.py +0 -0
  112. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/__init__.py +0 -0
  113. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/__main__.py +0 -0
  114. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/__init__.py +0 -0
  115. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/adapter.py +0 -0
  116. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/bases/__init__.py +0 -0
  117. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/bases/finder.py +0 -0
  118. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/module.py +0 -0
  119. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/__init__.py +0 -0
  120. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/adapter.py +0 -0
  121. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/bases/__init__.py +0 -0
  122. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/bases/loader.py +0 -0
  123. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/plugin_folder.py +0 -0
  124. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/strategy.py +0 -0
  125. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/strict.py +0 -0
  126. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/py.typed +0 -0
  127. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/config_schema.py +0 -0
  128. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/context.py +0 -0
  129. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/diagnostics.py +0 -0
  130. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/example_config.py +0 -0
  131. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/exceptions.py +0 -0
  132. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/file_watcher.py +0 -0
  133. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/frame_config.py +0 -0
  134. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/hints.py +0 -0
  135. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/memory.py +0 -0
  136. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/owner_cleanup.py +0 -0
  137. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/plugin_reload.py +0 -0
  138. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/proactive_gc.py +0 -0
  139. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/sdk_initializer.py +0 -0
  140. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/tasks.py +0 -0
  141. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/troubleshoot.py +0 -0
  142. {erispulse-2.9.0.dev1 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/version.py +0 -0
@@ -122,7 +122,7 @@ ipython_config.py
122
122
  # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
123
123
  # This is especially recommended for binary packages to ensure reproducibility, and is more
124
124
  # commonly ignored for libraries.
125
- #uv.lock
125
+ uv.lock
126
126
 
127
127
  # poetry
128
128
  # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
@@ -239,4 +239,8 @@ pyvenv.cfg
239
239
  # 但保留源中文文档
240
240
  !docs/zh-CN/
241
241
 
242
- .zcode/
242
+ .zcode/
243
+
244
+ .github/assets/ErisPulseFilm1.mp4
245
+ .github/assets/score1.wav
246
+ .github/assets/score2.wav
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ErisPulse
3
- Version: 2.9.0-dev.1
3
+ Version: 2.9.0-dev.2
4
4
  Summary: Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署
5
5
  Project-URL: Homepage, https://github.com/ErisPulse/ErisPulse
6
6
  Project-URL: Documentation, https://www.erisdev.com#docs
@@ -38,6 +38,7 @@ Requires-Dist: uvicorn>=0.35.0
38
38
  Requires-Dist: websockets>=12.0
39
39
  Provides-Extra: mysql
40
40
  Requires-Dist: aiomysql>=0.2.0; extra == 'mysql'
41
+ Requires-Dist: cryptography>=41.0.0; extra == 'mysql'
41
42
  Provides-Extra: postgres
42
43
  Requires-Dist: asyncpg>=0.29.0; extra == 'postgres'
43
44
  Provides-Extra: test
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ErisPulse"
7
- version = "2.9.0-dev.1"
7
+ version = "2.9.0-dev.2"
8
8
  description = "Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署"
9
9
  readme = "README.pypi.md"
10
10
  requires-python = ">=3.10"
@@ -84,6 +84,8 @@ Changelog = "https://github.com/ErisPulse/ErisPulse/blob/main/CHANGELOG.md"
84
84
  # 存储后端可选驱动:pip install ErisPulse[mysql] / ErisPulse[postgres]
85
85
  mysql = [
86
86
  "aiomysql>=0.2.0",
87
+ # MySQL 8 默认 caching_sha2_password 认证要求(缺失时连接池初始化失败)
88
+ "cryptography>=41.0.0",
87
89
  ]
88
90
  postgres = [
89
91
  "asyncpg>=0.29.0",
@@ -305,6 +305,11 @@ class CLI:
305
305
  args, unknown = self.parser.parse_known_args()
306
306
  args._unknown_args = unknown
307
307
 
308
+ # --yes/-y:assume-yes 模式,由统一交互封装(utils/interactive)消费
309
+ from .utils.interactive import set_assume_yes
310
+
311
+ set_assume_yes(bool(getattr(args, "yes", False)))
312
+
308
313
  # --no-color:禁用 Rich 控制台着色(CI / 日志采集场景)
309
314
  if getattr(args, "no_color", False):
310
315
  console.no_color = True
@@ -375,6 +380,13 @@ class CLI:
375
380
  console.print(f"\n[warning]{i18n.t('cli.run.user_interrupted')}[/]")
376
381
  sys.exit(1)
377
382
  except Exception as e:
383
+ from .utils.interactive import NonInteractiveError
384
+
385
+ if isinstance(e, NonInteractiveError):
386
+ # 非交互环境遇到无法自动应答的提示:明确报错而非 EOF 崩溃
387
+ console.print(f"[error]{e}[/]")
388
+ console.print(f"[hint]{i18n.t('cli.interactive.non_tty_hint')}[/]")
389
+ sys.exit(1)
378
390
  console.print(f"[error]{i18n.t('cli.run.exec_error', error=e)}[/]")
379
391
  # 场景化友好提示:根据异常类型给出下一步建议
380
392
  from .hints import suggest_for_exception
@@ -95,7 +95,7 @@ class DoctorCommand(Command):
95
95
  pip_note = (
96
96
  i18n.t("cli.doctor.venv_pip_ok")
97
97
  if venv_pip.exists()
98
- else "uv(pip 不可用,uv 后端替代)"
98
+ else i18n.t("cli.doctor.venv_pip_uv")
99
99
  )
100
100
  rows.append(
101
101
  (
@@ -133,6 +133,52 @@ class DoctorCommand(Command):
133
133
  )
134
134
  failed = True
135
135
 
136
+ # 4.5 组件配置健康(配置文件存在 ≠ 必填项齐全:复用配置向导的
137
+ # schema 状态检测,"装好了跑不起来"多因适配器必填项缺失)
138
+ try:
139
+ from ..utils.config_wizard import (
140
+ STATUS_INCOMPLETE,
141
+ get_target_status,
142
+ load_config_targets,
143
+ )
144
+
145
+ incomplete: list[str] = []
146
+ for target in load_config_targets():
147
+ status, _errors = get_target_status(target)
148
+ # 仅"待完善"(开了但必填项缺失)判 FAIL;完全未配置属
149
+ # 用户尚未开始配置,不算故障
150
+ if status == STATUS_INCOMPLETE:
151
+ incomplete.append(target.name)
152
+ if incomplete:
153
+ failed = True
154
+ rows.append(
155
+ (
156
+ self._status(False),
157
+ i18n.t("cli.doctor.component_config"),
158
+ i18n.t(
159
+ "cli.doctor.component_config_incomplete",
160
+ names=", ".join(incomplete),
161
+ ),
162
+ )
163
+ )
164
+ else:
165
+ rows.append(
166
+ (
167
+ self._status(True),
168
+ i18n.t("cli.doctor.component_config"),
169
+ i18n.t("cli.doctor.component_config_ok"),
170
+ )
171
+ )
172
+ except Exception:
173
+ # 发现失败(如依赖缺失)不阻塞其余诊断项
174
+ rows.append(
175
+ (
176
+ self._status(True),
177
+ i18n.t("cli.doctor.component_config"),
178
+ i18n.t("cli.doctor.component_config_skipped"),
179
+ )
180
+ )
181
+
136
182
  # 5. PyPI 连通性
137
183
  try:
138
184
  remote = asyncio.run(pm.get_remote_packages())
@@ -11,7 +11,6 @@ import sys
11
11
  from argparse import ArgumentParser
12
12
  from pathlib import Path
13
13
 
14
- from rich.prompt import Confirm, IntPrompt
15
14
  from rich.text import Text
16
15
 
17
16
  from ..base import Command
@@ -19,6 +18,7 @@ from ..console import console
19
18
  from ..i18n import i18n
20
19
  from ..utils import PackageManager
21
20
  from ..utils.display import _input, prompt_validated, section_header
21
+ from ..utils.interactive import confirm, select
22
22
  from ..utils.package_manager import (
23
23
  append_pyproject_dependencies,
24
24
  create_project_venv,
@@ -432,7 +432,11 @@ class InitCommand(Command):
432
432
  f" [bold]2.[/] {i18n.t('cli.init.location_option_new')} [dim]— {i18n.t('cli.init.location_desc_new')}[/]"
433
433
  )
434
434
  console.print()
435
- location_choice = IntPrompt.ask(i18n.t("cli.create.select_prompt"), default=2, choices=["1", "2"])
435
+ location_choice = int(
436
+ select(
437
+ i18n.t("cli.create.select_prompt"), choices=["1", "2"], default="2"
438
+ )
439
+ )
436
440
  console.print()
437
441
  in_current_dir = location_choice == 1
438
442
 
@@ -458,7 +462,7 @@ class InitCommand(Command):
458
462
  project_name = p.name
459
463
  project_path = (Path(target_dir) if target_dir else Path()) / project_name
460
464
  if project_path.exists() and not force:
461
- if not Confirm.ask(
465
+ if not confirm(
462
466
  f" [cyan]{i18n.t('cli.init.dir_overwrite_prompt', name=project_name)}[/]",
463
467
  default=False,
464
468
  ):
@@ -467,10 +471,10 @@ class InitCommand(Command):
467
471
 
468
472
  # 虚拟环境与 git 仓库问询
469
473
  if not getattr(self, "no_venv", False):
470
- create_venv = Confirm.ask(
474
+ create_venv = confirm(
471
475
  f" [cyan]{i18n.t('cli.init.venv_prompt')}[/]", default=True
472
476
  )
473
- git_init = Confirm.ask(
477
+ git_init = confirm(
474
478
  f" [cyan]{i18n.t('cli.init.git_prompt')}[/]", default=False
475
479
  )
476
480
 
@@ -521,7 +525,7 @@ class InitCommand(Command):
521
525
  console.print(f"[warning] {i18n.t('cli.init.invalid_port', port=new_port)}[/]")
522
526
  new_port = _input(">")
523
527
 
524
- if Confirm.ask(
528
+ if confirm(
525
529
  f"\n [cyan]{i18n.t('cli.init.configure_adapters_prompt')}[/]",
526
530
  default=True,
527
531
  ):
@@ -587,7 +591,7 @@ class InitCommand(Command):
587
591
 
588
592
  console.print(f"[dim] {i18n.t('cli.init.adapters_enabled', count=len(enabled))}[/]")
589
593
 
590
- if enabled and Confirm.ask(f" [cyan]{i18n.t('cli.init.install_selected_prompt')}[/]", default=True):
594
+ if enabled and confirm(f" [cyan]{i18n.t('cli.init.install_selected_prompt')}[/]", default=True):
591
595
  self._install_adapters(enabled, adapters)
592
596
 
593
597
  def _install_adapters(self, adapter_names, adapters_info):
@@ -8,7 +8,7 @@ import asyncio
8
8
  import sys
9
9
  from argparse import ArgumentParser
10
10
 
11
- from rich.prompt import Confirm, Prompt
11
+ from rich.prompt import Prompt
12
12
  from rich.text import Text
13
13
 
14
14
  from ..base import Command
@@ -16,6 +16,7 @@ from ..console import console
16
16
  from ..i18n import i18n
17
17
  from ..utils import PackageManager, config_wizard
18
18
  from ..utils.display import interactive_select_table
19
+ from ..utils.interactive import confirm
19
20
  from ..utils.package_manager import (
20
21
  resolve_target_python,
21
22
  warn_if_uv_isolated,
@@ -251,6 +252,7 @@ class InstallCommand(Command):
251
252
  if success:
252
253
  # 非交互路径不进入向导,仅提示后续配置入口
253
254
  console.print(f"[dim] {i18n.t('cli.install.configure_hint')}[/]")
255
+ console.print(f"[hint] {i18n.t('cli.install.done_hint')}[/]")
254
256
 
255
257
  if not success:
256
258
  sys.exit(1)
@@ -294,7 +296,7 @@ class InstallCommand(Command):
294
296
  elif choice == "4":
295
297
  self._install_custom(upgrade, pre)
296
298
 
297
- if not Confirm.ask(f"\n [cyan]{i18n.t('cli.install.continue_install')}[/]", default=False):
299
+ if not confirm(f"\n [cyan]{i18n.t('cli.install.continue_install')}[/]", default=False):
298
300
  break
299
301
 
300
302
  def _install_adapters(self, remote_packages: dict, upgrade: bool, pre: bool):
@@ -341,7 +343,7 @@ class InstallCommand(Command):
341
343
 
342
344
  selected_names = [name for name, _ in selected]
343
345
  console.print(f"\n [dim]{i18n.t('cli.install.selected', selected=', '.join(selected_names))}[/]")
344
- if Confirm.ask(
346
+ if confirm(
345
347
  f" [cyan]{i18n.t('cli.install.confirm_adapters', count=len(selected_names))}[/]",
346
348
  default=True,
347
349
  ):
@@ -390,7 +392,7 @@ class InstallCommand(Command):
390
392
 
391
393
  selected_names = [name for name, _ in selected]
392
394
  console.print(f"\n [dim]{i18n.t('cli.install.selected', selected=', '.join(selected_names))}[/]")
393
- if Confirm.ask(
395
+ if confirm(
394
396
  f" [cyan]{i18n.t('cli.install.confirm_modules', count=len(selected_names))}[/]",
395
397
  default=True,
396
398
  ):
@@ -495,7 +497,7 @@ class InstallCommand(Command):
495
497
  except ValueError:
496
498
  continue
497
499
 
498
- if selected and Confirm.ask(
500
+ if selected and confirm(
499
501
  f" [cyan]{i18n.t('cli.install.confirm_search_install', count=len(selected))}[/]",
500
502
  default=True,
501
503
  ):
@@ -514,7 +516,7 @@ class InstallCommand(Command):
514
516
  if package_name.lower() == "q":
515
517
  return
516
518
  if package_name:
517
- if Confirm.ask(
519
+ if confirm(
518
520
  f" [cyan]{i18n.t('cli.install.confirm_custom_install', package_name=package_name)}[/]",
519
521
  default=True,
520
522
  ):
@@ -534,6 +536,7 @@ class InstallCommand(Command):
534
536
  """
535
537
  if not config_wizard.is_interactive():
536
538
  console.print(f"[dim] {i18n.t('cli.install.configure_hint')}[/]")
539
+ console.print(f"[hint] {i18n.t('cli.install.done_hint')}[/]")
537
540
  return
538
541
 
539
542
  resolved = []
@@ -545,3 +548,4 @@ class InstallCommand(Command):
545
548
  config_wizard.post_install_configure(resolved)
546
549
  except Exception as e:
547
550
  console.print(f"[warning] {i18n.t('cli.config.post_install_failed', error=e)}[/]")
551
+ console.print(f"[hint] {i18n.t('cli.install.done_hint')}[/]")
@@ -56,6 +56,7 @@ class ListCommand(Command):
56
56
 
57
57
  # 仅在需要时一次性拉取远程索引,避免逐包重复 asyncio.run / 网络请求
58
58
  remote_packages = None
59
+ remote_unavailable = False
59
60
  if outdated_only:
60
61
  try:
61
62
  remote_packages = asyncio.run(
@@ -63,15 +64,29 @@ class ListCommand(Command):
63
64
  )
64
65
  except Exception:
65
66
  remote_packages = None
67
+ # 索引拉取失败(异常或双源均空):区分"全是最新"与"无法检查"
68
+ remote_unavailable = remote_packages is None or not (
69
+ remote_packages.get("modules") or remote_packages.get("adapters")
70
+ )
66
71
 
67
72
  if pkg_type == "all":
68
- self._print_installed_packages("modules", outdated_only, remote_packages)
69
- self._print_installed_packages("adapters", outdated_only, remote_packages)
73
+ self._print_installed_packages(
74
+ "modules", outdated_only, remote_packages, remote_unavailable
75
+ )
76
+ self._print_installed_packages(
77
+ "adapters", outdated_only, remote_packages, remote_unavailable
78
+ )
70
79
  else:
71
- self._print_installed_packages(pkg_type, outdated_only, remote_packages)
80
+ self._print_installed_packages(
81
+ pkg_type, outdated_only, remote_packages, remote_unavailable
82
+ )
72
83
 
73
84
  def _print_installed_packages(
74
- self, pkg_type: str, outdated_only: bool = False, remote_packages: dict | None = None
85
+ self,
86
+ pkg_type: str,
87
+ outdated_only: bool = False,
88
+ remote_packages: dict | None = None,
89
+ remote_unavailable: bool = False,
75
90
  ):
76
91
  """
77
92
  以表格形式打印已安装的模块或适配器
@@ -79,6 +94,7 @@ class ListCommand(Command):
79
94
  :param pkg_type: [str] 组件类型 (modules 或 adapters)
80
95
  :param outdated_only: [bool] 是否仅显示可升级的包 (默认: False)
81
96
  :param remote_packages: [Optional[dict]] 预取的远程索引,避免逐包重复拉取 (默认: None)
97
+ :param remote_unavailable: [bool] 远程索引是否不可达(空结果区分文案用,默认: False)
82
98
  """
83
99
  installed = self.package_manager.get_installed_packages()
84
100
 
@@ -121,6 +137,10 @@ class ListCommand(Command):
121
137
  )
122
138
  # 展示模块注册的脚本入口
123
139
  self._print_package_scripts(installed["modules"])
140
+ elif outdated_only and remote_unavailable:
141
+ console.print(f"[dim] {i18n.t('cli.list.outdated_unavailable')}[/]")
142
+ elif outdated_only:
143
+ console.print(f"[dim] {i18n.t('cli.list.no_outdated')}[/]")
124
144
  else:
125
145
  console.print(f"[dim] {i18n.t('cli.list.no_modules')}[/]")
126
146
  console.print(f"[dim] {i18n.t('cli.list.empty_hint')}[/]")
@@ -134,6 +154,7 @@ class ListCommand(Command):
134
154
  )
135
155
  table.add_column(i18n.t("cli.list.header_package"), min_width=20)
136
156
  table.add_column(i18n.t("cli.list.header_version"), width=10)
157
+ table.add_column(i18n.t("cli.list.header_status"), width=8)
137
158
  table.add_column(i18n.t("cli.list.header_desc"))
138
159
 
139
160
  count = 0
@@ -142,10 +163,16 @@ class ListCommand(Command):
142
163
  info["package"], info["version"], remote_packages
143
164
  ):
144
165
  continue
166
+ status = (
167
+ f"[green]{i18n.t('cli.list.status_enabled')}[/]"
168
+ if info.get("enabled", True)
169
+ else f"[yellow]{i18n.t('cli.list.status_disabled')}[/]"
170
+ )
145
171
  table.add_row(
146
172
  name,
147
173
  info["package"],
148
174
  info["version"],
175
+ status,
149
176
  info["summary"],
150
177
  )
151
178
  count += 1
@@ -155,6 +182,10 @@ class ListCommand(Command):
155
182
  console.print(
156
183
  f"[dim] {i18n.t('cli.list.count_adapters', count=count)}[/]"
157
184
  )
185
+ elif outdated_only and remote_unavailable:
186
+ console.print(f"[dim] {i18n.t('cli.list.outdated_unavailable')}[/]")
187
+ elif outdated_only:
188
+ console.print(f"[dim] {i18n.t('cli.list.no_outdated')}[/]")
158
189
  else:
159
190
  console.print(f"[dim] {i18n.t('cli.list.no_adapters')}[/]")
160
191
  console.print(f"[dim] {i18n.t('cli.list.empty_hint')}[/]")
@@ -182,12 +213,15 @@ class ListCommand(Command):
182
213
  """
183
214
  if remote_packages is None:
184
215
  remote_packages = asyncio.run(self.package_manager.get_remote_packages())
216
+ # 仅"远端更新"才算可升级:本地 dev/git 安装版本比远端新时不再误报
217
+ from ...runtime.version import compare_versions
218
+
185
219
  for module_info in remote_packages["modules"].values():
186
220
  if module_info["package"] == package_name:
187
- return module_info["version"] != current_version
221
+ return compare_versions(module_info["version"], current_version) > 0
188
222
  for adapter_info in remote_packages["adapters"].values():
189
223
  if adapter_info["package"] == package_name:
190
- return adapter_info["version"] != current_version
224
+ return compare_versions(adapter_info["version"], current_version) > 0
191
225
  return False
192
226
 
193
227
  def _print_package_scripts(self, packages: dict) -> None:
@@ -24,6 +24,7 @@ from ..constants import (
24
24
  HARD_RESTART_EXIT_CODE,
25
25
  RUN_CRASH_BACKOFF_MAX_SECS,
26
26
  RUN_CRASH_BACKOFF_PER_CRASH_SECS,
27
+ RUN_PERSISTENT_CRASH_HINT_THRESHOLD,
27
28
  RUN_RESTART_PAUSE_SECS,
28
29
  )
29
30
  from ..i18n import i18n
@@ -205,6 +206,11 @@ class RunCommand(Command):
205
206
  ]
206
207
 
207
208
  crash_count = 0
209
+ # 连续同退出码崩溃计数:达到阈值输出一次排查指引(确定性故障
210
+ # 退避重试无意义,与其反复失败不如引导留现场)
211
+ consecutive_same_code = 0
212
+ last_crash_code: int | None = None
213
+ crash_hint_shown = False
208
214
  process = None
209
215
  try:
210
216
  while True:
@@ -229,6 +235,20 @@ class RunCommand(Command):
229
235
  # 非硬重启/非正常退出码:模块/适配器内部错误导致子进程异常终止
230
236
  # 不退出主进程,等待后自动重试
231
237
  crash_count += 1
238
+ if process.returncode == last_crash_code:
239
+ consecutive_same_code += 1
240
+ else:
241
+ consecutive_same_code = 1
242
+ last_crash_code = process.returncode
243
+ if (
244
+ not crash_hint_shown
245
+ and RUN_PERSISTENT_CRASH_HINT_THRESHOLD > 0
246
+ and consecutive_same_code >= RUN_PERSISTENT_CRASH_HINT_THRESHOLD
247
+ ):
248
+ crash_hint_shown = True
249
+ console.print(
250
+ f"[hint]{i18n.t('cli.run.persistent_crash_hint', count=consecutive_same_code, code=process.returncode)}[/]"
251
+ )
232
252
  backoff = min(RUN_CRASH_BACKOFF_MAX_SECS, RUN_CRASH_BACKOFF_PER_CRASH_SECS * crash_count)
233
253
  console.print(
234
254
  f"[warning]{i18n.t('cli.run.process_crashed', code=process.returncode)}[/]"
@@ -9,7 +9,7 @@ import sys
9
9
  from argparse import ArgumentParser
10
10
 
11
11
  from rich.box import SIMPLE
12
- from rich.prompt import Confirm, Prompt
12
+ from rich.prompt import Prompt
13
13
  from rich.table import Table
14
14
  from rich.text import Text
15
15
 
@@ -18,6 +18,7 @@ from ..console import console
18
18
  from ..i18n import i18n
19
19
  from ..utils import PackageManager
20
20
  from ..utils.display import _input, section_header
21
+ from ..utils.interactive import confirm
21
22
 
22
23
 
23
24
  class SelfUpdateCommand(Command):
@@ -82,7 +83,7 @@ class SelfUpdateCommand(Command):
82
83
  )
83
84
  sys.exit(0)
84
85
  elif not args.force:
85
- if not Confirm.ask(
86
+ if not confirm(
86
87
  i18n.t(
87
88
  "cli.self_update.confirm_update",
88
89
  current=current_version,
@@ -114,7 +115,7 @@ class SelfUpdateCommand(Command):
114
115
  console.print(
115
116
  f"[warning]{i18n.t('cli.self_update.version_not_found', version=specified_version)}[/]"
116
117
  )
117
- if not Confirm.ask(
118
+ if not confirm(
118
119
  i18n.t("cli.self_update.confirm_continue"), default=False
119
120
  ):
120
121
  return None
@@ -197,7 +198,7 @@ class SelfUpdateCommand(Command):
197
198
  console.print(
198
199
  f"[warning]{i18n.t('cli.self_update.version_not_found', version=target_version)}[/]"
199
200
  )
200
- if not Confirm.ask(
201
+ if not confirm(
201
202
  i18n.t("cli.self_update.confirm_continue"), default=False
202
203
  ):
203
204
  return None
@@ -7,11 +7,11 @@ Upgrade 命令实现
7
7
  import sys
8
8
  from argparse import ArgumentParser
9
9
 
10
- from rich.prompt import Confirm
11
-
12
10
  from ..base import Command
11
+ from ..console import console
13
12
  from ..i18n import i18n
14
13
  from ..utils import PackageManager
14
+ from ..utils.interactive import confirm
15
15
  from ..utils.package_manager import resolve_target_python, warn_if_uv_isolated
16
16
 
17
17
 
@@ -50,15 +50,21 @@ class UpgradeCommand(Command):
50
50
  def execute(self, args):
51
51
  warn_if_uv_isolated()
52
52
  self.package_manager.no_uv = getattr(args, "no_uv", False)
53
+ upgraded = False
53
54
  if args.package:
54
55
  # 升级指定包
55
56
  success = self.package_manager.upgrade_package(args.package, pre=args.pre)
56
57
  if not success:
57
58
  sys.exit(1)
59
+ upgraded = True
58
60
  # 升级所有包
59
- elif args.force or Confirm.ask(
61
+ elif args.force or confirm(
60
62
  i18n.t("cli.upgrade.confirm_all"), default=False
61
63
  ):
62
64
  success = self.package_manager.upgrade_all()
63
65
  if not success:
64
66
  sys.exit(1)
67
+ upgraded = True
68
+ if upgraded:
69
+ # 生效方式提示:升级只更新磁盘代码,运行中的进程需重启或热重载
70
+ console.print(f"[hint]{i18n.t('cli.upgrade.done_hint')}[/]")
@@ -50,12 +50,17 @@ PYPI_PACKAGE_JSON_URL_TEMPLATE: str = "https://pypi.org/pypi/{package}/json"
50
50
  RUN_CRASH_BACKOFF_MAX_SECS: float = 60.0
51
51
  RUN_CRASH_BACKOFF_PER_CRASH_SECS: float = 3.0
52
52
  RUN_RESTART_PAUSE_SECS: float = 0.5
53
+ # 连续同退出码崩溃达到该次数后,输出一次排查指引(疑似确定性故障,
54
+ # 退避重试无意义)。使用位置: CLI/commands/run.py -> 监督循环;
55
+ # 修改影响: 数值越小越早提示,越大越保守(0/负数禁用提示)。
56
+ RUN_PERSISTENT_CRASH_HINT_THRESHOLD: int = 3
53
57
 
54
58
  __all__ = [
55
59
  "ADAPTER_ENTRY_POINT_GROUP",
56
60
  "RUN_CRASH_BACKOFF_MAX_SECS",
57
61
  "RUN_CRASH_BACKOFF_PER_CRASH_SECS",
58
62
  "RUN_RESTART_PAUSE_SECS",
63
+ "RUN_PERSISTENT_CRASH_HINT_THRESHOLD",
59
64
  "ENV_SUPERVISED",
60
65
  "HARD_RESTART_EXIT_CODE",
61
66
  "MODULE_ENTRY_POINT_GROUP",
@@ -280,10 +280,13 @@ TRANSLATIONS = {
280
280
  "cli.install.custom_prompt": "Enter package name to install (or q to go back)",
281
281
  "cli.install.confirm_custom_install": "Confirm install {package_name}?",
282
282
  "cli.install.configure_hint": "Run epsdk config to set up credentials/params of newly installed components",
283
+ "cli.install.done_hint": "Installed. Restart the running instance or call sdk.reload_all_modules() to apply",
283
284
  # ==================== list command ====================
284
285
  "cli.list.description": "List installed components",
285
286
  "cli.list.type_help": "List type (default: all)",
286
287
  "cli.list.outdated_help": "Show upgradable packages only",
288
+ "cli.list.no_outdated": "All components are up to date",
289
+ "cli.list.outdated_unavailable": "Component index unreachable; cannot check for updates (network issue or index source temporarily unavailable)",
287
290
  "cli.list.header_module": "Module Name",
288
291
  "cli.list.header_package": "Package",
289
292
  "cli.list.header_version": "Version",
@@ -373,6 +376,7 @@ TRANSLATIONS = {
373
376
  "cli.run.process_crashed": "Process crashed (exit code {code}), fix and save to restart",
374
377
  "cli.run.process_exited": "Child process exited; run finished",
375
378
  "cli.run.subprocess_crashed_retry": "Subprocess crashed, will auto-retry in {seconds}s...",
379
+ "cli.run.persistent_crash_hint": "Crashed {count} consecutive times with the same exit code ({code}) — likely a persistent failure; retrying is futile. Run epsdk doctor to check the environment; set ErisPulse.logger.log_dir = \"logs\" in config.toml to keep crash logs; use --verbose for full stack traces",
376
380
  "cli.run.terminating_child": "Terminating the bot subprocess to release ports and resources...",
377
381
  # ==================== uninstall command ====================
378
382
  "cli.uninstall.description": "Uninstall module/adapter packages",
@@ -394,6 +398,10 @@ TRANSLATIONS = {
394
398
  "cli.upgrade.pre_help": "Include pre-release versions",
395
399
  "cli.upgrade.no_uv_help": "Disable uv, force upgrade via pip",
396
400
  "cli.upgrade.confirm_all": "Are you sure you want to upgrade all ErisPulse components?",
401
+ "cli.upgrade.done_hint": "Upgraded. Restart the running instance or call sdk.reload_module(name, full=True) to apply",
402
+ # ==================== interactive unified prompt helpers ====================
403
+ "cli.interactive.non_tty": "Non-interactive environment detected (stdin is not a TTY); cannot answer interactive prompts",
404
+ "cli.interactive.non_tty_hint": "Use --yes/-y to auto-confirm all interactive prompts, or run inside a terminal (TTY)",
397
405
  # ==================== language command ====================
398
406
  "cli.language.description": "View / switch CLI display language",
399
407
  "cli.language.lang_help": "Target language code (e.g. zh-CN, en, ja, ru)",
@@ -509,6 +517,11 @@ TRANSLATIONS = {
509
517
  "cli.doctor.venv": "project virtual environment",
510
518
  "cli.doctor.venv_missing": "not detected (current interpreter will be used)",
511
519
  "cli.doctor.venv_pip_ok": "pip available",
520
+ "cli.doctor.venv_pip_uv": "uv (pip unavailable, uv backend as fallback)",
521
+ "cli.doctor.component_config": "Component config health",
522
+ "cli.doctor.component_config_ok": "All ready (nothing pending)",
523
+ "cli.doctor.component_config_incomplete": "Pending: {names} (run epsdk config <name> to fill required fields)",
524
+ "cli.doctor.component_config_skipped": "Component discovery failed; this check was skipped",
512
525
  "cli.doctor.config": "Config file",
513
526
  "cli.doctor.config_missing": "not found",
514
527
  "cli.doctor.pypi": "PyPI reachability",
@@ -280,10 +280,13 @@ TRANSLATIONS = {
280
280
  "cli.install.custom_prompt": "インストールするパッケージ名を入力(q で戻る)",
281
281
  "cli.install.confirm_custom_install": "{package_name} をインストールしますか?",
282
282
  "cli.install.configure_hint": "epsdk config を実行して新規インストールしたコンポーネントの認証情報/パラメーターを設定してください",
283
+ "cli.install.done_hint": "インストール完了。実行中のインスタンスは再起動するか sdk.reload_all_modules() でホットリロードすると反映されます",
283
284
  # ==================== list コマンド ====================
284
285
  "cli.list.description": "インストール済みコンポーネントを一覧表示",
285
286
  "cli.list.type_help": "一覧タイプ (デフォルト: all)",
286
287
  "cli.list.outdated_help": "アップグレード可能なパッケージのみ表示",
288
+ "cli.list.no_outdated": "すべてのコンポーネントは最新です",
289
+ "cli.list.outdated_unavailable": "コンポーネント索引に到達できず、更新を確認できません(ネットワーク問題または索引ソースの一時的な障害)",
287
290
  "cli.list.header_module": "モジュール名",
288
291
  "cli.list.header_package": "パッケージ",
289
292
  "cli.list.header_version": "バージョン",
@@ -373,6 +376,7 @@ TRANSLATIONS = {
373
376
  "cli.run.process_crashed": "プロセスが異常終了しました(終了コード {code})。修正して保存すると自動再起動",
374
377
  "cli.run.process_exited": "サブプロセスが終了しました。実行を終了します",
375
378
  "cli.run.subprocess_crashed_retry": "サブプロセスが異常終了しました。{seconds} 秒後に自動再試行します...",
379
+ "cli.run.persistent_crash_hint": "同じ終了コード({code})で {count} 回連続クラッシュ——恒久的な障害の可能性が高く、再試行は無意味です。epsdk doctor で環境を確認するか、config.toml に ErisPulse.logger.log_dir = \"logs\" を設定してクラッシュ時のログを残し、--verbose で完全なスタックトレースを確認してください",
376
380
  "cli.run.terminating_child": "ポートなどのリソースを解放するため、ボットの子プロセスを終了しています...",
377
381
  # ==================== uninstall コマンド ====================
378
382
  "cli.uninstall.description": "モジュール/アダプターパッケージをアンインストール",
@@ -394,6 +398,10 @@ TRANSLATIONS = {
394
398
  "cli.upgrade.pre_help": "プレリリース版を含める",
395
399
  "cli.upgrade.no_uv_help": "uv を無効にし、pip でのアップグレードを強制",
396
400
  "cli.upgrade.confirm_all": "すべての ErisPulse コンポーネントをアップグレードしますか?",
401
+ "cli.upgrade.done_hint": "アップグレード完了。実行中のインスタンスは再起動するか sdk.reload_module(name, full=True) でホットリロードすると反映されます",
402
+ # ==================== interactive 統一プロンプトヘルパー ====================
403
+ "cli.interactive.non_tty": "非対話環境を検出しました(stdin が TTY ではありません)。対話プロンプトに応答できません",
404
+ "cli.interactive.non_tty_hint": "--yes/-y ですべての対話プロンプトを自動確認するか、ターミナル(TTY)内で実行してください",
397
405
  # ==================== language コマンド ====================
398
406
  "cli.language.description": "CLI 表示言語の確認 / 切り替え",
399
407
  "cli.language.lang_help": "対象言語コード (例: zh-CN, en, ja, ru)",
@@ -509,6 +517,11 @@ TRANSLATIONS = {
509
517
  "cli.doctor.venv": "プロジェクト仮想環境",
510
518
  "cli.doctor.venv_missing": "未検出(現在のインタープリターを使用)",
511
519
  "cli.doctor.venv_pip_ok": "pip 利用可能",
520
+ "cli.doctor.venv_pip_uv": "uv(pip 利用不可、uv バックエンドで代替)",
521
+ "cli.doctor.component_config": "コンポーネント設定の健全性",
522
+ "cli.doctor.component_config_ok": "すべて準備完了(未完了項目なし)",
523
+ "cli.doctor.component_config_incomplete": "要対応: {names}(epsdk config <名称> で必須項目を入力してください)",
524
+ "cli.doctor.component_config_skipped": "コンポーネント検出に失敗したため、このチェックをスキップしました",
512
525
  "cli.doctor.config": "設定ファイル",
513
526
  "cli.doctor.config_missing": "見つかりません",
514
527
  "cli.doctor.pypi": "PyPI 接続",