ErisPulse 2.9.0.dev1__tar.gz → 2.10.0.dev0__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 (146) hide show
  1. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/.gitignore +6 -2
  2. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/PKG-INFO +4 -1
  3. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/README.pypi.md +2 -0
  4. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/pyproject.toml +3 -1
  5. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/cli.py +12 -0
  6. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/create.py +84 -16
  7. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/doctor.py +47 -1
  8. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/init.py +28 -22
  9. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/install.py +10 -6
  10. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/list.py +40 -6
  11. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/run.py +20 -0
  12. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/self_update.py +5 -4
  13. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/types.py +27 -31
  14. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/upgrade.py +9 -3
  15. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/constants.py +13 -0
  16. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/i18n/locales/en.py +27 -0
  17. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/i18n/locales/ja.py +27 -0
  18. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/i18n/locales/ru.py +27 -0
  19. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +27 -0
  20. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +27 -0
  21. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/utils/config_wizard.py +7 -6
  22. erispulse-2.10.0.dev0/src/ErisPulse/CLI/utils/interactive.py +127 -0
  23. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/utils/package_manager.py +16 -9
  24. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/utils/scaffold_text.py +62 -0
  25. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/__init__.py +16 -1
  26. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/adapter.py +13 -12
  27. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/client.py +25 -3
  28. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/errors.py +87 -0
  29. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/kv_builder.py +4 -2
  30. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/router.py +100 -12
  31. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/sql_base.py +11 -6
  32. erispulse-2.10.0.dev0/src/ErisPulse/Core/Bases/websocket.py +477 -0
  33. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/base.py +22 -19
  34. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/command.py +6 -4
  35. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/conversation.py +62 -13
  36. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/governance.py +10 -4
  37. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/overrides.py +64 -40
  38. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/__init__.py +28 -0
  39. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/adapter.py +111 -11
  40. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/client.py +35 -4
  41. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/config.py +359 -13
  42. erispulse-2.10.0.dev0/src/ErisPulse/Core/connections.py +555 -0
  43. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/constants.py +142 -1
  44. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/i18n/__init__.py +94 -19
  45. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/i18n/locales/en.py +42 -2
  46. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/i18n/locales/ja.py +42 -2
  47. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/i18n/locales/ru.py +42 -2
  48. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/i18n/locales/zh_cn.py +42 -2
  49. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/i18n/locales/zh_tw.py +42 -2
  50. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/lifecycle.py +10 -0
  51. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/logger.py +80 -9
  52. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/module.py +56 -3
  53. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/ownership.py +98 -30
  54. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/router.py +422 -53
  55. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/scope.py +3 -3
  56. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/shadow.py +15 -14
  57. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/text_match.py +3 -1
  58. erispulse-2.10.0.dev0/src/ErisPulse/Core/transcript.py +817 -0
  59. erispulse-2.10.0.dev0/src/ErisPulse/__init__.py +426 -0
  60. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/loaders/module.py +168 -8
  61. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/loaders/plugin_folder.py +5 -1
  62. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/__init__.py +17 -1
  63. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/sdk_initializer.py +9 -0
  64. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/tasks.py +232 -3
  65. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/troubleshoot.py +38 -30
  66. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/sdk.py +30 -3
  67. erispulse-2.9.0.dev1/src/ErisPulse/Core/Bases/websocket.py +0 -271
  68. erispulse-2.9.0.dev1/src/ErisPulse/Core/transcript.py +0 -426
  69. erispulse-2.9.0.dev1/src/ErisPulse/__init__.py +0 -108
  70. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/LICENSE +0 -0
  71. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/__init__.py +0 -0
  72. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/base.py +0 -0
  73. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/__init__.py +0 -0
  74. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/config.py +0 -0
  75. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/language.py +0 -0
  76. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
  77. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
  78. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/commands/version.py +0 -0
  79. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/console.py +0 -0
  80. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/hints.py +0 -0
  81. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
  82. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
  83. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/registry.py +0 -0
  84. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/utils/__init__.py +0 -0
  85. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/CLI/utils/display.py +0 -0
  86. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/api_dsl.py +0 -0
  87. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/config_schema.py +0 -0
  88. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/converter.py +0 -0
  89. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
  90. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/manager.py +0 -0
  91. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/model.py +0 -0
  92. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/module.py +0 -0
  93. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
  94. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
  95. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Bases/storage.py +0 -0
  96. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/__init__.py +0 -0
  97. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/command_args.py +0 -0
  98. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/interaction.py +0 -0
  99. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/message.py +0 -0
  100. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/message_builder.py +0 -0
  101. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/meta.py +0 -0
  102. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/notice.py +0 -0
  103. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/request.py +0 -0
  104. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/session_type.py +0 -0
  105. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/throttle.py +0 -0
  106. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/trace.py +0 -0
  107. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/Event/wrapper.py +0 -0
  108. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/assets/__init__.py +0 -0
  109. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/assets/error.css +0 -0
  110. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/assets/error.html +0 -0
  111. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/assets/root.css +0 -0
  112. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/assets/root.html +0 -0
  113. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/di.py +0 -0
  114. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/i18n/constants.py +0 -0
  115. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
  116. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/master.py +0 -0
  117. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/storage/__init__.py +0 -0
  118. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/storage/mysql.py +0 -0
  119. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/storage/postgres.py +0 -0
  120. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/Core/storage/sqlite.py +0 -0
  121. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/__main__.py +0 -0
  122. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/finders/__init__.py +0 -0
  123. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/finders/adapter.py +0 -0
  124. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/finders/bases/__init__.py +0 -0
  125. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/finders/bases/finder.py +0 -0
  126. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/finders/module.py +0 -0
  127. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/loaders/__init__.py +0 -0
  128. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/loaders/adapter.py +0 -0
  129. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/loaders/bases/__init__.py +0 -0
  130. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/loaders/bases/loader.py +0 -0
  131. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/loaders/strategy.py +0 -0
  132. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/loaders/strict.py +0 -0
  133. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/py.typed +0 -0
  134. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/config_schema.py +0 -0
  135. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/context.py +0 -0
  136. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/diagnostics.py +0 -0
  137. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/example_config.py +0 -0
  138. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/exceptions.py +0 -0
  139. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/file_watcher.py +0 -0
  140. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/frame_config.py +0 -0
  141. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/hints.py +0 -0
  142. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/memory.py +0 -0
  143. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/owner_cleanup.py +0 -0
  144. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/plugin_reload.py +0 -0
  145. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/src/ErisPulse/runtime/proactive_gc.py +0 -0
  146. {erispulse-2.9.0.dev1 → erispulse-2.10.0.dev0}/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.10.0-dev.0
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
@@ -94,6 +95,8 @@ It ships with a flexible plugin system, hot reload, a visual Dashboard, an AI Bu
94
95
  pip install ErisPulse
95
96
  ```
96
97
 
98
+ For a global `epsdk` CLI without any virtual environment, use `uv tool install ErisPulse` instead.
99
+
97
100
  ### Option 2 — Docker (recommended for production)
98
101
 
99
102
  ```bash
@@ -42,6 +42,8 @@ It ships with a flexible plugin system, hot reload, a visual Dashboard, an AI Bu
42
42
  pip install ErisPulse
43
43
  ```
44
44
 
45
+ For a global `epsdk` CLI without any virtual environment, use `uv tool install ErisPulse` instead.
46
+
45
47
  ### Option 2 — Docker (recommended for production)
46
48
 
47
49
  ```bash
@@ -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.10.0-dev.0"
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
@@ -41,6 +41,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
41
41
  SOFTWARE.
42
42
  """
43
43
 
44
+ # 生成模块/适配器模板的最低 SDK 地板(写入模板 min_sdk_version 字段)。
45
+ # 模板示例使用 args=/options=(2.9 特性);pip 默认不解析预发布版本,
46
+ # 该地板不得高于已发布的正式版本,否则生成的组件会因依赖装不上或
47
+ # 加载器按 PEP 440 拒载(与 CLI/constants.py 的 INIT_ERISPULSE_DEPENDENCY 同步升版)。
48
+ _TEMPLATE_MIN_SDK_VERSION = "2.9.0"
49
+
44
50
  _MODULE_PYPROJECT = """[project]
45
51
  name = "ErisPulse-{name}"
46
52
  version = "1.0.0"
@@ -72,10 +78,19 @@ _MODULE_INIT = """from .Core import Main
72
78
 
73
79
  _MODULE_CORE = """from dataclasses import dataclass, field
74
80
 
75
- from ErisPulse import SDK
76
- from ErisPulse.Core.Bases import BaseConfig, BaseI18n, BaseModule, I18nKey, ModuleMeta
77
- from ErisPulse.Core.Event import Event, command, message, notice
78
- from ErisPulse.Core.i18n import i18n
81
+ from ErisPulse import (
82
+ SDK,
83
+ BaseConfig,
84
+ BaseI18n,
85
+ BaseModule,
86
+ Event,
87
+ I18nKey,
88
+ ModuleMeta,
89
+ command,
90
+ i18n,
91
+ message,
92
+ notice,
93
+ )
79
94
 
80
95
 
81
96
  class Main(BaseModule):
@@ -144,6 +159,42 @@ class Main(BaseModule):
144
159
  ru=\"Привет от {name}!\",
145
160
  zh_TW=\"來自 {name} 的問候!\",
146
161
  )
162
+ roll_help: I18nKey = I18nKey(
163
+ key=\"module.{name}.command.roll.help\",
164
+ default=\"Roll dice: count [sides]\",
165
+ zh_CN=\"掷骰子:次数 [面数]\",
166
+ en=\"Roll dice: count [sides]\",
167
+ ja=\"サイコロを振る: 回数 [面数]\",
168
+ ru=\"Бросить кости: количество [граней]\",
169
+ zh_TW=\"擲骰子:次數 [面數]\",
170
+ )
171
+ roll_reply: I18nKey = I18nKey(
172
+ key=\"module.{name}.command.roll.reply\",
173
+ default=\"Rolled {{count}}d{{sides}}, total: {{total}}{{detail}}\",
174
+ zh_CN=\"掷了 {{count}} 次 {{sides}} 面骰,总点数:{{total}}{{detail}}\",
175
+ en=\"Rolled {{count}}d{{sides}}, total: {{total}}{{detail}}\",
176
+ ja=\"{{sides}}面ダイスを {{count}} 回振った合計: {{total}}{{detail}}\",
177
+ ru=\"Бросков {{count}}d{{sides}}, сумма: {{total}}{{detail}}\",
178
+ zh_TW=\"擲了 {{count}} 次 {{sides}} 面骰,總點數:{{total}}{{detail}}\",
179
+ )
180
+ roll_detail: I18nKey = I18nKey(
181
+ key=\"module.{name}.command.roll.detail\",
182
+ default=\" (verbose: {{count}} rolls of {{sides}}-sided dice)\",
183
+ zh_CN=\"(详细模式:{{count}} 次 {{sides}} 面骰)\",
184
+ en=\" (verbose: {{count}} rolls of {{sides}}-sided dice)\",
185
+ ja=\"(詳細モード: {{sides}}面ダイスを {{count}} 回)\",
186
+ ru=\" (подробно: {{count}} бросков {{sides}}-гранного кубика)\",
187
+ zh_TW=\"(詳細模式:{{count}} 次 {{sides}} 面骰)\",
188
+ )
189
+ signin_reply: I18nKey = I18nKey(
190
+ key=\"module.{name}.command.signin.reply\",
191
+ default=\"Signed in!\",
192
+ zh_CN=\"签到成功\",
193
+ en=\"Signed in!\",
194
+ ja=\"チェックイン完了!\",
195
+ ru=\"Готово, вы отмечены!\",
196
+ zh_TW=\"簽到成功\",
197
+ )
147
198
 
148
199
  def __init__(self, sdk: SDK = None):
149
200
  # sdk 由框架在实例化时自动注入(无需手动导入兜底)
@@ -167,7 +218,7 @@ class Main(BaseModule):
167
218
  version=\"0.1.0\",
168
219
  author=\"ErisDev\",
169
220
  # 最低 SDK 版本(可选):不满足时框架在加载期明确报错并跳过本模块
170
- min_sdk_version=\"2.8.0\",
221
+ min_sdk_version=\"{min_sdk}\",
171
222
  group=\"default\",
172
223
  tags=[\"{name}\"],
173
224
  # 对外服务白名单(可选):声明后其他模块可经 sdk.module.call() 调用这些方法;
@@ -225,6 +276,20 @@ class Main(BaseModule):
225
276
  async def hello_command(event: Event):
226
277
  await event.reply(i18n.t(\"module.{name}.command.hello.reply\"))
227
278
 
279
+ # {text[module.roll_hint]}
280
+ @command(
281
+ \"roll\",
282
+ args=\"<count:int> [sides:int=6]\",
283
+ options={{\"verbose\": \"-v/--verbose\"}},
284
+ help=i18n.t(\"module.{name}.command.roll.help\"),
285
+ )
286
+ async def roll_command(event: Event, count: int, sides: int = 6, verbose: bool = False):
287
+ import random
288
+
289
+ total = sum(random.randint(1, sides) for _ in range(count))
290
+ detail = i18n.t(\"module.{name}.command.roll.detail\", count=count, sides=sides) if verbose else \"\"
291
+ await event.reply(i18n.t(\"module.{name}.command.roll.reply\", count=count, sides=sides, total=total, detail=detail))
292
+
228
293
  async def _register_message_handlers(self):
229
294
  @message.on_private_message()
230
295
  async def private_message_handler(event: Event):
@@ -235,9 +300,9 @@ class Main(BaseModule):
235
300
  pass
236
301
 
237
302
  # 消息装饰器支持 pattern(glob 通配符)/ regex(正则),不匹配则不触发
238
- @message.on_message(pattern=\"签到*\")
303
+ @message.on_message(pattern=\"{text[module.signin_pattern]}\")
239
304
  async def signin_handler(event: Event):
240
- await event.reply(\"签到成功\")
305
+ await event.reply(i18n.t(\"module.{name}.command.signin.reply\"))
241
306
 
242
307
  @notice.on_friend_add()
243
308
  async def friend_add_handler(event: Event):
@@ -283,10 +348,7 @@ _ADAPTER_CORE = """import asyncio
283
348
  import json
284
349
  from dataclasses import dataclass, field
285
350
  from typing import ClassVar
286
- from ErisPulse.Core import BaseAdapter
287
- from ErisPulse.Core.Bases import BaseConfig, BaseI18n, I18nKey
288
- from ErisPulse.Core import router
289
- from ErisPulse.Core.i18n import i18n
351
+ from ErisPulse import BaseAdapter, BaseConfig, BaseI18n, I18nKey, router, i18n
290
352
 
291
353
 
292
354
  class {name}(BaseAdapter):
@@ -294,7 +356,7 @@ class {name}(BaseAdapter):
294
356
  {text[adapter.doc]}
295
357
  \"\"\"
296
358
 
297
- # 依赖声明(可选,ErisPulse 2.8.0+):
359
+ # 依赖声明(可选,ErisPulse 2.9.0+):
298
360
  # depends = {{"adapters": [], "modules": []}} # 硬依赖:缺失时跳过启动
299
361
  # optional_modules = [] # 软依赖:就绪/丢失时收到
300
362
  # # on_dependency_ready/lost 回调
@@ -302,7 +364,7 @@ class {name}(BaseAdapter):
302
364
  optional_modules: ClassVar[list] = []
303
365
 
304
366
  # 声明所需最低 SDK 版本:不满足时框架在加载期明确报错并跳过本适配器(可选)
305
- min_sdk_version = "2.8.0"
367
+ min_sdk_version = "{min_sdk}"
306
368
 
307
369
  # {text[adapter.config_hint]}
308
370
  @dataclass
@@ -461,6 +523,11 @@ class {name}(BaseAdapter):
461
523
  async def _ws_handler(self, websocket):
462
524
  bot_id = self._get_bot_id()
463
525
 
526
+ # 连接已由框架自动登记进连接池(ErisPulse.connections):
527
+ # meta 供跨模块查看,join_group 后可被 connections.broadcast 定向推送
528
+ websocket.meta["bot_id"] = bot_id
529
+ websocket.join_group(f"bot:{{bot_id}}")
530
+
464
531
  await self.emit_meta("connect", bot_id, user_name="{name}")
465
532
  self.logger.info("{text[adapter.log.bot_connected]}")
466
533
 
@@ -484,7 +551,7 @@ class {name}(BaseAdapter):
484
551
 
485
552
  async def call_api(self, endpoint: str, **params):
486
553
  \"\"\"Call platform API\"\"\"
487
- from ErisPulse.Core import client
554
+ from ErisPulse import client
488
555
 
489
556
  cfg = self.cfg
490
557
  headers = {{"Authorization": "Bearer " + cfg.token}}
@@ -848,7 +915,7 @@ class CreateCommand(Command):
848
915
 
849
916
  (pkg_dir / "__init__.py").write_text(_MODULE_INIT, encoding="utf-8")
850
917
  (pkg_dir / "Core.py").write_text(
851
- _MODULE_CORE.format(name=name, text=_scaffold_text(name)),
918
+ _MODULE_CORE.format(name=name, text=_scaffold_text(name), min_sdk=_TEMPLATE_MIN_SDK_VERSION),
852
919
  encoding="utf-8",
853
920
  )
854
921
  (project_dir / "pyproject.toml").write_text(
@@ -921,7 +988,7 @@ class CreateCommand(Command):
921
988
 
922
989
  (plugin_dir / "__init__.py").write_text(_MODULE_INIT, encoding="utf-8")
923
990
  (plugin_dir / "Core.py").write_text(
924
- _MODULE_CORE.format(name=name, text=_scaffold_text(name)),
991
+ _MODULE_CORE.format(name=name, text=_scaffold_text(name), min_sdk=_TEMPLATE_MIN_SDK_VERSION),
925
992
  encoding="utf-8",
926
993
  )
927
994
 
@@ -1000,6 +1067,7 @@ class CreateCommand(Command):
1000
1067
  converter_name=converter_name,
1001
1068
  entry_key=entry_key,
1002
1069
  text=_scaffold_text(name),
1070
+ min_sdk=_TEMPLATE_MIN_SDK_VERSION,
1003
1071
  ),
1004
1072
  encoding="utf-8",
1005
1073
  )
@@ -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,14 +11,15 @@ 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
18
17
  from ..console import console
18
+ from ..constants import INIT_ERISPULSE_DEPENDENCY
19
19
  from ..i18n import i18n
20
20
  from ..utils import PackageManager
21
21
  from ..utils.display import _input, prompt_validated, section_header
22
+ from ..utils.interactive import confirm, select
22
23
  from ..utils.package_manager import (
23
24
  append_pyproject_dependencies,
24
25
  create_project_venv,
@@ -26,6 +27,7 @@ from ..utils.package_manager import (
26
27
  uv_add,
27
28
  warn_if_uv_isolated,
28
29
  )
30
+ from ..utils.scaffold_text import ScaffoldText
29
31
 
30
32
 
31
33
  def _validate_project_name(name: str) -> bool:
@@ -192,6 +194,9 @@ class InitCommand(Command):
192
194
  console.print(f"[success] {i18n.t('cli.init.created_dir', name=project_name)}[/]")
193
195
 
194
196
  try:
197
+ # 生成物文案(config 注释 / main docstring / .gitignore 分节 /
198
+ # README)跟随 CLI 语言;缺失键回退英文
199
+ st = ScaffoldText()
195
200
  for dir_name in ["config", "logs"]:
196
201
  (project_path / dir_name).mkdir(exist_ok=True)
197
202
  # SSL 证书默认目录:跟随项目运行目录走,配置里用相对路径引用
@@ -201,15 +206,14 @@ class InitCommand(Command):
201
206
  ssl_file = ssl_dir / ssl_name
202
207
  if not ssl_file.exists():
203
208
  ssl_file.write_text(
204
- "# 将你的证书/密钥 PEM 内容粘贴到本文件,或在配置中改用 ssl_cert/ssl_key 内联填写\n",
209
+ st.t("init.ssl_placeholder") + "\n",
205
210
  encoding="utf-8",
206
211
  )
207
212
 
208
213
  config_file = project_path / "config" / "config.toml"
209
214
  if not config_file.exists():
210
215
  with config_file.open("w", encoding="utf-8") as f:
211
- f.write("# ErisPulse 配置文件\n")
212
- f.write("# 完整配置示例请参考 config.full.example\n\n")
216
+ f.write(st.t("init.config_toml_header") + "\n\n")
213
217
  f.write("[ErisPulse.server]\n")
214
218
  f.write('host = "0.0.0.0"\n')
215
219
  f.write("port = 8000\n\n")
@@ -224,9 +228,7 @@ class InitCommand(Command):
224
228
  main_file = project_path / "main.py"
225
229
  if not main_file.exists():
226
230
  with main_file.open("w", encoding="utf-8") as f:
227
- f.write(f'"""\n{display_name} 主程序\n\n')
228
- f.write("这是 ErisPulse 自动生成的主程序文件\n")
229
- f.write('"""\n\n')
231
+ f.write(st.t("init.main_docstring", display_name=display_name) + "\n")
230
232
  f.write("import asyncio\n")
231
233
  f.write("from ErisPulse import sdk\n\n")
232
234
  f.write("async def main():\n")
@@ -245,7 +247,7 @@ class InitCommand(Command):
245
247
  f.write('description = "ErisPulse project"\n')
246
248
  f.write('requires-python = ">=3.10"\n')
247
249
  f.write("dependencies = [\n")
248
- f.write(' "erispulse>=2.8.3",\n')
250
+ f.write(f' "{INIT_ERISPULSE_DEPENDENCY}",\n')
249
251
  for adapter in adapter_list or []:
250
252
  f.write(f' "{adapter}",\n')
251
253
  f.write("]\n")
@@ -265,13 +267,13 @@ class InitCommand(Command):
265
267
  "*.egg-info/\n"
266
268
  ".eggs/\n"
267
269
  "\n"
268
- "# 虚拟环境与本地环境\n"
270
+ f"{st.t('init.gitignore_section_venv')}\n"
269
271
  ".venv/\n"
270
272
  "venv/\n"
271
273
  "env/\n"
272
274
  ".env\n"
273
275
  "\n"
274
- "# 工具缓存\n"
276
+ f"{st.t('init.gitignore_section_cache')}\n"
275
277
  ".pytest_cache/\n"
276
278
  ".mypy_cache/\n"
277
279
  ".ruff_cache/\n"
@@ -279,11 +281,11 @@ class InitCommand(Command):
279
281
  "coverage.xml\n"
280
282
  "htmlcov/\n"
281
283
  "\n"
282
- "# ErisPulse 运行时数据(配置与日志含敏感信息,不入库)\n"
284
+ f"{st.t('init.gitignore_section_runtime')}\n"
283
285
  "config/\n"
284
286
  "logs/\n"
285
287
  "\n"
286
- "# 编辑器与系统文件\n"
288
+ f"{st.t('init.gitignore_section_editor')}\n"
287
289
  ".idea/\n"
288
290
  ".vscode/\n"
289
291
  "*.swp\n"
@@ -295,14 +297,14 @@ class InitCommand(Command):
295
297
  readme_file = project_path / "README.md"
296
298
  if not readme_file.exists():
297
299
  with readme_file.open("w", encoding="utf-8") as f:
298
- f.write(f"# {display_name}\n\nErisPulse 项目。\n\n```bash\nepsdk run\n```\n")
300
+ f.write(st.t("init.readme_body", display_name=display_name))
299
301
  console.print(f"[success] {i18n.t('cli.init.readme_created')}[/]")
300
302
 
301
303
  # ---- 虚拟环境与依赖安装 ----
302
304
  if create_venv:
303
305
  console.print(f"[info] {i18n.t('cli.init.venv_creating')}[/]")
304
306
  venv_python = None
305
- if uv_add(project_path, ["erispulse>=2.8.3"]):
307
+ if uv_add(project_path, [INIT_ERISPULSE_DEPENDENCY]):
306
308
  # uv add 自动创建 .venv、安装依赖并写入 pyproject 依赖清单
307
309
  venv_python = resolve_target_python(project_path)[0]
308
310
  console.print(f"[success] {i18n.t('cli.init.venv_created')}[/]")
@@ -312,8 +314,8 @@ class InitCommand(Command):
312
314
  if venv_python:
313
315
  console.print(f"[success] {i18n.t('cli.init.venv_created')}[/]")
314
316
  pm = PackageManager(python_executable=venv_python)
315
- if pm.install_package(["erispulse>=2.8.3"]):
316
- append_pyproject_dependencies(project_path, ["erispulse>=2.8.3"])
317
+ if pm.install_package([INIT_ERISPULSE_DEPENDENCY]):
318
+ append_pyproject_dependencies(project_path, [INIT_ERISPULSE_DEPENDENCY])
317
319
  console.print(f"[success] {i18n.t('cli.init.deps_installed')}[/]")
318
320
  else:
319
321
  console.print(
@@ -432,7 +434,11 @@ class InitCommand(Command):
432
434
  f" [bold]2.[/] {i18n.t('cli.init.location_option_new')} [dim]— {i18n.t('cli.init.location_desc_new')}[/]"
433
435
  )
434
436
  console.print()
435
- location_choice = IntPrompt.ask(i18n.t("cli.create.select_prompt"), default=2, choices=["1", "2"])
437
+ location_choice = int(
438
+ select(
439
+ i18n.t("cli.create.select_prompt"), choices=["1", "2"], default="2"
440
+ )
441
+ )
436
442
  console.print()
437
443
  in_current_dir = location_choice == 1
438
444
 
@@ -458,7 +464,7 @@ class InitCommand(Command):
458
464
  project_name = p.name
459
465
  project_path = (Path(target_dir) if target_dir else Path()) / project_name
460
466
  if project_path.exists() and not force:
461
- if not Confirm.ask(
467
+ if not confirm(
462
468
  f" [cyan]{i18n.t('cli.init.dir_overwrite_prompt', name=project_name)}[/]",
463
469
  default=False,
464
470
  ):
@@ -467,10 +473,10 @@ class InitCommand(Command):
467
473
 
468
474
  # 虚拟环境与 git 仓库问询
469
475
  if not getattr(self, "no_venv", False):
470
- create_venv = Confirm.ask(
476
+ create_venv = confirm(
471
477
  f" [cyan]{i18n.t('cli.init.venv_prompt')}[/]", default=True
472
478
  )
473
- git_init = Confirm.ask(
479
+ git_init = confirm(
474
480
  f" [cyan]{i18n.t('cli.init.git_prompt')}[/]", default=False
475
481
  )
476
482
 
@@ -521,7 +527,7 @@ class InitCommand(Command):
521
527
  console.print(f"[warning] {i18n.t('cli.init.invalid_port', port=new_port)}[/]")
522
528
  new_port = _input(">")
523
529
 
524
- if Confirm.ask(
530
+ if confirm(
525
531
  f"\n [cyan]{i18n.t('cli.init.configure_adapters_prompt')}[/]",
526
532
  default=True,
527
533
  ):
@@ -587,7 +593,7 @@ class InitCommand(Command):
587
593
 
588
594
  console.print(f"[dim] {i18n.t('cli.init.adapters_enabled', count=len(enabled))}[/]")
589
595
 
590
- if enabled and Confirm.ask(f" [cyan]{i18n.t('cli.init.install_selected_prompt')}[/]", default=True):
596
+ if enabled and confirm(f" [cyan]{i18n.t('cli.init.install_selected_prompt')}[/]", default=True):
591
597
  self._install_adapters(enabled, adapters)
592
598
 
593
599
  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')}[/]")