ErisPulse 2.8.0.dev0__tar.gz → 2.8.0.dev1__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 (119) hide show
  1. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/.gitignore +1 -0
  2. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/PKG-INFO +5 -28
  3. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/README.pypi.md +3 -1
  4. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/pyproject.toml +20 -6
  5. erispulse-2.8.0.dev1/src/ErisPulse/CLI/commands/config.py +208 -0
  6. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/create.py +25 -0
  7. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/init.py +39 -80
  8. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/install.py +68 -93
  9. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/i18n/locales/en.py +72 -3
  10. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/i18n/locales/ja.py +72 -3
  11. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/i18n/locales/ru.py +72 -3
  12. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +72 -3
  13. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +72 -3
  14. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/utils/__init__.py +2 -0
  15. erispulse-2.8.0.dev1/src/ErisPulse/CLI/utils/config_wizard.py +859 -0
  16. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/adapter.py +520 -141
  17. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/i18n_schema.py +1 -0
  18. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/__init__.py +4 -0
  19. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/base.py +85 -48
  20. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/command.py +330 -97
  21. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/message.py +45 -8
  22. erispulse-2.8.0.dev1/src/ErisPulse/Core/Event/overrides.py +727 -0
  23. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/session_type.py +45 -1
  24. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/wrapper.py +15 -1
  25. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/__init__.py +6 -1
  26. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/adapter.py +190 -4
  27. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/config.py +109 -28
  28. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/constants.py +103 -16
  29. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/i18n/__init__.py +10 -6
  30. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/i18n/locales/en.py +34 -12
  31. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/i18n/locales/ja.py +34 -12
  32. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/i18n/locales/ru.py +34 -12
  33. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/i18n/locales/zh_cn.py +34 -12
  34. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/i18n/locales/zh_tw.py +34 -12
  35. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/master.py +143 -13
  36. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/module.py +221 -166
  37. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/router.py +68 -4
  38. erispulse-2.8.0.dev1/src/ErisPulse/Core/scope.py +1171 -0
  39. erispulse-2.8.0.dev1/src/ErisPulse/Core/text_match.py +195 -0
  40. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/loaders/module.py +206 -85
  41. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/__init__.py +2 -0
  42. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/frame_config.py +58 -7
  43. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/plugin_reload.py +1 -1
  44. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/sdk.py +47 -15
  45. erispulse-2.8.0.dev0/src/ErisPulse/Core/scope.py +0 -537
  46. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/LICENSE +0 -0
  47. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/__init__.py +0 -0
  48. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/base.py +0 -0
  49. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/cli.py +0 -0
  50. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/__init__.py +0 -0
  51. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/doctor.py +0 -0
  52. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/language.py +0 -0
  53. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/list.py +0 -0
  54. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
  55. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/run.py +0 -0
  56. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/self_update.py +0 -0
  57. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/types.py +0 -0
  58. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
  59. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
  60. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/console.py +0 -0
  61. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/constants.py +0 -0
  62. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/hints.py +0 -0
  63. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
  64. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
  65. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/registry.py +0 -0
  66. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/utils/display.py +0 -0
  67. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/utils/file_watcher.py +0 -0
  68. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
  69. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/CLI/utils/scaffold_text.py +0 -0
  70. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/__init__.py +0 -0
  71. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/client.py +0 -0
  72. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/config_schema.py +0 -0
  73. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/converter.py +0 -0
  74. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/errors.py +0 -0
  75. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/kv_builder.py +0 -0
  76. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/manager.py +0 -0
  77. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/module.py +0 -0
  78. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/router.py +0 -0
  79. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
  80. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
  81. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/storage.py +0 -0
  82. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Bases/websocket.py +0 -0
  83. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/message_builder.py +0 -0
  84. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/meta.py +0 -0
  85. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/notice.py +0 -0
  86. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/Event/request.py +0 -0
  87. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/assets/__init__.py +0 -0
  88. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/assets/error.css +0 -0
  89. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/assets/error.html +0 -0
  90. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/assets/root.css +0 -0
  91. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/assets/root.html +0 -0
  92. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/client.py +0 -0
  93. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/i18n/constants.py +0 -0
  94. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
  95. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/lifecycle.py +0 -0
  96. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/logger.py +0 -0
  97. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/Core/storage.py +0 -0
  98. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/__init__.py +0 -0
  99. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/__main__.py +0 -0
  100. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/finders/__init__.py +0 -0
  101. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/finders/adapter.py +0 -0
  102. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/finders/bases/__init__.py +0 -0
  103. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/finders/bases/finder.py +0 -0
  104. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/finders/module.py +0 -0
  105. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/loaders/__init__.py +0 -0
  106. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/loaders/adapter.py +0 -0
  107. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/loaders/bases/__init__.py +0 -0
  108. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/loaders/bases/loader.py +0 -0
  109. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/loaders/plugin_folder.py +0 -0
  110. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/loaders/strategy.py +0 -0
  111. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/loaders/strict.py +0 -0
  112. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/py.typed +0 -0
  113. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/config_schema.py +0 -0
  114. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/context.py +0 -0
  115. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/diagnostics.py +0 -0
  116. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/exceptions.py +0 -0
  117. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/hints.py +0 -0
  118. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/memory.py +0 -0
  119. {erispulse-2.8.0.dev0 → erispulse-2.8.0.dev1}/src/ErisPulse/runtime/tasks.py +0 -0
@@ -1,6 +1,7 @@
1
1
  # dev gnore ignore file for Python projects
2
2
  devs/videos
3
3
  main.py
4
+ plugins/
4
5
  venvs
5
6
  config.toml
6
7
  uv.lock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ErisPulse
3
- Version: 2.8.0.dev0
3
+ Version: 2.8.0.dev1
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
@@ -10,27 +10,7 @@ Project-URL: Bug Tracker, https://github.com/ErisPulse/ErisPulse/blob/main/docs/
10
10
  Project-URL: Changelog, https://github.com/ErisPulse/ErisPulse/blob/main/CHANGELOG.md
11
11
  Author: ErisDev
12
12
  Maintainer-email: "艾莉丝·格雷拉特(WSu2059)" <wsu2059@qq.com>
13
- License: MIT License
14
-
15
- Copyright (c) 2025-2026 ErisPulse
16
-
17
- Permission is hereby granted, free of charge, to any person obtaining a copy
18
- of this software and associated documentation files (the "Software"), to deal
19
- in the Software without restriction, including without limitation the rights
20
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21
- copies of the Software, and to permit persons to whom the Software is
22
- furnished to do so, subject to the following conditions:
23
-
24
- The above copyright notice and this permission notice shall be included in all
25
- copies or substantial portions of the Software.
26
-
27
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
- SOFTWARE.
13
+ License-Expression: MIT
34
14
  License-File: LICENSE
35
15
  Keywords: async,asyncio,bot,bot-framework,chatbot,email,extensible,framework,instant-messaging,kook,matrix,modular,multi-platform,onebot,onebot12,qq,robot,sdk,telegram,yunhu
36
16
  Classifier: Development Status :: 4 - Beta
@@ -54,11 +34,6 @@ Requires-Dist: rich>=13.0.0
54
34
  Requires-Dist: toml>=0.10.2
55
35
  Requires-Dist: uvicorn>=0.30.0
56
36
  Requires-Dist: websockets>=12.0
57
- Provides-Extra: dev
58
- Requires-Dist: basedpyright>=1.20.0; extra == 'dev'
59
- Requires-Dist: pyclean>=3.6.0; extra == 'dev'
60
- Requires-Dist: ruff>=0.1.7; extra == 'dev'
61
- Requires-Dist: typing-extensions>=4.7.0; (python_version < '3.11') and extra == 'dev'
62
37
  Provides-Extra: test
63
38
  Requires-Dist: httpx>=0.24.0; extra == 'test'
64
39
  Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
@@ -99,7 +74,9 @@ It ships with a flexible plugin system, hot reload, a visual Dashboard, an AI Bu
99
74
  - **Event-driven architecture** — Clear event model based on OneBot12, making message handling intuitive and consistent across platforms.
100
75
  - **Cross-platform compatibility** — Write handlers once, deploy to 15+ platforms without rewriting business logic.
101
76
  - **Modular design** — Flexible plugin system with hot-plug module management and per-module configuration.
102
- - **Hot reload** — Iterate on code during development without restarting the bot process.
77
+ - **Hot reload** — Local plugins reload on save; any module (including PyPI-installed ones) hot-reloads via a single `sdk.reload_module()` call — no process restart, even after pip upgrades.
78
+ - **Unified Event Overrides** — Override message triggers, command params and user permissions for *every* event type with a single config — no module code changes, takes effect instantly.
79
+ - **Scope Control** — Three-dimensional scope (module availability / event admission / outbound limits) with runtime-adjustable fine-grained control.
103
80
  - **Chainable SendDSL** — Express `@mention`, reply, retry, timeout, progress hook and bulk send as a single fluent chain.
104
81
  - **Complete toolchain** — `epsdk` CLI, visual Dashboard, AI Builder and Module Market included out of the box.
105
82
 
@@ -28,7 +28,9 @@ It ships with a flexible plugin system, hot reload, a visual Dashboard, an AI Bu
28
28
  - **Event-driven architecture** — Clear event model based on OneBot12, making message handling intuitive and consistent across platforms.
29
29
  - **Cross-platform compatibility** — Write handlers once, deploy to 15+ platforms without rewriting business logic.
30
30
  - **Modular design** — Flexible plugin system with hot-plug module management and per-module configuration.
31
- - **Hot reload** — Iterate on code during development without restarting the bot process.
31
+ - **Hot reload** — Local plugins reload on save; any module (including PyPI-installed ones) hot-reloads via a single `sdk.reload_module()` call — no process restart, even after pip upgrades.
32
+ - **Unified Event Overrides** — Override message triggers, command params and user permissions for *every* event type with a single config — no module code changes, takes effect instantly.
33
+ - **Scope Control** — Three-dimensional scope (module availability / event admission / outbound limits) with runtime-adjustable fine-grained control.
32
34
  - **Chainable SendDSL** — Express `@mention`, reply, retry, timeout, progress hook and bulk send as a single fluent chain.
33
35
  - **Complete toolchain** — `epsdk` CLI, visual Dashboard, AI Builder and Module Market included out of the box.
34
36
 
@@ -1,14 +1,15 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ requires = ["hatchling>=1.26"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ErisPulse"
7
- version = "2.8.0-dev.0"
7
+ version = "2.8.0-dev.1"
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"
11
- license = { file = "LICENSE" }
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
12
13
 
13
14
  authors = [
14
15
  { name = "ErisDev" }
@@ -87,11 +88,14 @@ test = [
87
88
  "pytest-benchmark>=4.0.0",
88
89
  "httpx>=0.24.0",
89
90
  ]
91
+
92
+ [dependency-groups]
90
93
  dev = [
91
94
  "pyclean>=3.6.0",
92
95
  "ruff>=0.1.7",
93
96
  "basedpyright>=1.20.0",
94
- "typing_extensions>=4.7.0; python_version < '3.11'"]
97
+ "typing_extensions>=4.7.0; python_version < '3.11'",
98
+ ]
95
99
 
96
100
  [project.scripts]
97
101
  ep-init = "ErisPulse.__init__:init_sync"
@@ -118,7 +122,7 @@ include = [
118
122
  target-version = "py310"
119
123
  line-length = 120
120
124
 
121
- include = ["src/**/*.py"]
125
+ include = ["src/**/*.py", "tests/**/*.py"]
122
126
 
123
127
  extend-exclude = [
124
128
  "__pycache__",
@@ -161,6 +165,7 @@ ignore = [
161
165
  "PLR0912", # too-many-branches
162
166
  "PLR0913", # too-many-arguments
163
167
  "PLR0915", # too-many-statements
168
+ "PLR0917", # too-many-positional-arguments —— 同 PLR0913,既有公共 API 签名不可破坏
164
169
  "PLR2004", # magic-value-comparison —— 数值阈值判定过于严格
165
170
  "PLR1714", # repeated-equality-comparison
166
171
  "PLW0602", # global-variable-not-assigned
@@ -169,6 +174,7 @@ ignore = [
169
174
  "PERF203", # try-except-in-loop —— 异步轮询/重试场景的常见且必要写法,性能影响极小
170
175
  "RUF012", # mutable-class-default —— 类属性容器(handlers/事件表)是项目既有 API
171
176
  "RUF005", # collection-literal-concatenation —— `list + list` 有时比 unpack 更清晰
177
+ "RUF022", # unsorted-dunder-all —— __all__ 常按语义分组并附加注释,强制排序破坏可读性
172
178
  "RET504", # unnecessary-assign —— “先 assign 再 return”有时更易调试
173
179
 
174
180
  # 风格偏好(不强加于现有代码)
@@ -194,7 +200,15 @@ ignore = [
194
200
 
195
201
  [tool.ruff.lint.per-file-ignores]
196
202
  "__init__.py" = ["F401", "PLC0415", "I001"] # 导出式 __init__ 经常按需导入
197
- "tests/**/*.py" = ["S101", "PLR2004", "SLF001", "ARG", "ANN", "PTH"]
203
+ "tests/**/*.py" = [
204
+ # 断言/夹具相关(测试代码天然模式)
205
+ "S101", "PLR2004", "SLF001", "ARG", "ANN", "PTH",
206
+ "E712", "B011", "B017", "B018", "RUF043",
207
+ # 测试内常见的宽松写法
208
+ "E702", "E402", "F841", "RUF059", "PLW0108",
209
+ "UP031", "N814", "PERF401", "PLR0917", "RUF015",
210
+ "B007", "PIE810", "PLR0124", "PLW1510", "SIM118",
211
+ ]
198
212
 
199
213
  [tool.ruff.lint.pep8-naming]
200
214
  # 装饰器方法、classmethod 的首参数名允许保留
@@ -0,0 +1,208 @@
1
+ """
2
+ Config 命令实现
3
+
4
+ 交互式配置适配器/模块(schema 驱动向导,含适配器多账户管理)
5
+ """
6
+
7
+ import json
8
+ from argparse import ArgumentParser
9
+
10
+ from rich.box import SIMPLE
11
+ from rich.prompt import Prompt
12
+ from rich.table import Table
13
+
14
+ from ..base import Command
15
+ from ..console import console
16
+ from ..i18n import i18n
17
+ from ..utils import config_wizard
18
+ from ..utils.config_wizard import (
19
+ STATUS_INCOMPLETE,
20
+ STATUS_NONE,
21
+ STATUS_OK,
22
+ STATUS_UNCONFIGURED,
23
+ )
24
+ from ..utils.display import section_header
25
+
26
+
27
+ class ConfigCommand(Command):
28
+ """
29
+ config 命令
30
+
31
+ 交互式配置适配器/模块;适配器支持多账户(bot 账户)管理
32
+ """
33
+
34
+ name = "config"
35
+ description = i18n.t("cli.config.description")
36
+ aliases = ["cfg", "conf"]
37
+
38
+ def add_arguments(self, parser: ArgumentParser):
39
+ parser.add_argument(
40
+ "name",
41
+ nargs="?",
42
+ help=i18n.t("cli.config.name_help"),
43
+ )
44
+ parser.add_argument(
45
+ "--list",
46
+ "-l",
47
+ action="store_true",
48
+ help=i18n.t("cli.config.list_help"),
49
+ )
50
+ parser.add_argument(
51
+ "--json",
52
+ action="store_true",
53
+ help=i18n.t("cli.config.json_help"),
54
+ )
55
+
56
+ def execute(self, args):
57
+ from ErisPulse import config
58
+
59
+ targets = config_wizard.load_config_targets()
60
+ if not targets:
61
+ console.print(f"[dim] {i18n.t('cli.config.no_targets')}[/]")
62
+ console.print(f"[dim] {i18n.t('cli.config.install_hint')}[/]")
63
+ return
64
+
65
+ if getattr(args, "json", False):
66
+ self._print_status_json(targets, config)
67
+ return
68
+
69
+ if args.name:
70
+ self._run_named(targets, args.name, config)
71
+ return
72
+
73
+ self._print_status_table(targets, config)
74
+
75
+ if args.list:
76
+ return
77
+
78
+ if not config_wizard.is_interactive():
79
+ console.print(f"[dim] {i18n.t('cli.config.non_interactive_hint')}[/]")
80
+ return
81
+
82
+ self._interactive_select(targets, config)
83
+
84
+ def _print_status_json(self, targets, config):
85
+ """
86
+ 以 JSON 输出全部目标及其配置状态(供 CI / 脚本消费)
87
+
88
+ :param targets: ConfigTarget 列表
89
+ :param config: ConfigManager 实例
90
+ """
91
+ result = {}
92
+ for target in sorted(targets, key=lambda t: (t.kind != "adapter", t.name.lower())):
93
+ status, errors = config_wizard.get_target_status(target, config)
94
+ result[target.name] = {
95
+ "kind": target.kind,
96
+ "config_key": target.config_key,
97
+ "status": status,
98
+ "errors": errors,
99
+ }
100
+ console.print_json(json.dumps(result, ensure_ascii=False, indent=2))
101
+
102
+ def _run_named(self, targets, name: str, config):
103
+ """
104
+ 按名称定位目标并直接进入向导
105
+
106
+ :param targets: ConfigTarget 列表
107
+ :param name: 目标名(适配器平台名/模块名,或适配器配置键)
108
+ :param config: ConfigManager 实例
109
+ """
110
+ for target in targets:
111
+ if target.name == name or target.config_key == name:
112
+ if not target.configurable:
113
+ console.print(f"[warning] {i18n.t('cli.config.no_declaration', name=target.name)}[/]")
114
+ return
115
+ config_wizard.run_wizard(target, config)
116
+ return
117
+ console.print(f"[error] {i18n.t('cli.config.not_found', name=name)}[/]")
118
+
119
+ def _status_text(self, status: str) -> str:
120
+ """
121
+ 将状态常量渲染为带颜色的显示文本
122
+
123
+ :param status: get_target_status 返回的状态常量
124
+ :return: rich 标记的状态文本
125
+ """
126
+ mapping = {
127
+ STATUS_OK: ("cli.config.status_ok", "green"),
128
+ STATUS_INCOMPLETE: ("cli.config.status_incomplete", "yellow"),
129
+ STATUS_UNCONFIGURED: ("cli.config.status_unconfigured", "cyan"),
130
+ STATUS_NONE: ("cli.config.status_none", "dim"),
131
+ }
132
+ key, style = mapping.get(status, ("cli.config.status_none", "dim"))
133
+ return f"[{style}]{i18n.t(key)}[/]"
134
+
135
+ def _print_status_table(self, targets, config):
136
+ """
137
+ 打印全部目标及其配置状态表
138
+
139
+ :param targets: ConfigTarget 列表
140
+ :param config: ConfigManager 实例
141
+ """
142
+ table = Table(box=SIMPLE, show_lines=False, header_style="bold", pad_edge=False)
143
+ table.add_column(i18n.t("cli.config.header_kind"), width=8)
144
+ table.add_column(i18n.t("cli.config.header_name"), min_width=12)
145
+ table.add_column(i18n.t("cli.config.header_status"), width=10)
146
+ table.add_column(i18n.t("cli.config.header_detail"))
147
+
148
+ for target in sorted(targets, key=lambda t: (t.kind != "adapter", t.name.lower())):
149
+ status, errors = config_wizard.get_target_status(target, config)
150
+ detail = ";".join(errors[:3])
151
+ if len(errors) > 3:
152
+ detail += f" (+{len(errors) - 3})"
153
+ kind_style = "adapter" if target.kind == "adapter" else "module"
154
+ table.add_row(
155
+ f"[{kind_style}]{target.kind_label}[/]",
156
+ target.name,
157
+ self._status_text(status),
158
+ detail,
159
+ )
160
+
161
+ console.print(table)
162
+
163
+ def _interactive_select(self, targets, config):
164
+ """
165
+ 交互式选择目标并进入向导(可连续配置多个)
166
+
167
+ :param targets: ConfigTarget 列表
168
+ :param config: ConfigManager 实例
169
+ """
170
+ selectable = [t for t in targets if t.configurable]
171
+ if not selectable:
172
+ console.print(f"[dim] {i18n.t('cli.config.no_configurable')}[/]")
173
+ return
174
+
175
+ selectable.sort(key=lambda t: (t.kind != "adapter", t.name.lower()))
176
+
177
+ while True:
178
+ section_header(i18n.t("cli.config.select_section"))
179
+ for idx, target in enumerate(selectable, 1):
180
+ status, _ = config_wizard.get_target_status(target, config)
181
+ kind_style = "adapter" if target.kind == "adapter" else "module"
182
+ console.print(f" [bold]{idx}.[/] [{kind_style}]{target.name}[/] {self._status_text(status)}")
183
+
184
+ try:
185
+ raw = Prompt.ask(
186
+ f" {i18n.t('cli.config.select_prompt')}",
187
+ default="",
188
+ show_default=False,
189
+ )
190
+ except (EOFError, KeyboardInterrupt):
191
+ break
192
+
193
+ raw = raw.strip()
194
+ if not raw:
195
+ break
196
+
197
+ try:
198
+ idx = int(raw)
199
+ except ValueError:
200
+ console.print(f"[warning] {i18n.t('cli.config.invalid_index', idx=raw)}[/]")
201
+ continue
202
+
203
+ if not 1 <= idx <= len(selectable):
204
+ console.print(f"[warning] {i18n.t('cli.config.invalid_index', idx=idx)}[/]")
205
+ continue
206
+
207
+ config_wizard.run_wizard(selectable[idx - 1], config)
208
+ # 向导结束后回到选择菜单(状态重新刷新),支持连续配置多个目标
@@ -58,6 +58,13 @@ dependencies = [
58
58
 
59
59
  [project.entry-points]
60
60
  "erispulse.module" = {{ "{name}" = "{name}:Main" }}
61
+
62
+ [build-system]
63
+ requires = ["hatchling"]
64
+ build-backend = "hatchling.build"
65
+
66
+ [tool.hatch.build.targets.wheel]
67
+ packages = ["{name}"]
61
68
  """
62
69
 
63
70
  _MODULE_INIT = """from .Core import Main
@@ -192,6 +199,12 @@ class Main(BaseModule):
192
199
  self.logger.info(\"{text[module.log.config_updated]}\")
193
200
 
194
201
  async def _register_commands(self):
202
+ # 命令权限(可选):permission 为调用函数,返回 True 才执行命令;
203
+ # master=True 限定框架主人(仅开发者默认值,用户可在统一覆写系统
204
+ # ErisPulse.event.overrides.command.<module>.<cmd>.master = true/false 覆写收紧或放开);
205
+ # 跨命令的用户黑白名单用覆写系统 ACL
206
+ # (ErisPulse.event.overrides.acl 或 overrides.acl.set(name, allow/deny),命令名支持 glob);
207
+ # 模块级可用性 / 事件准入 / 出站限制由作用域 scope 管理(用户可控)
195
208
  @command(\"hello\", help=i18n.t(\"module.{name}.command.hello.help\"))
196
209
  async def hello_command(event: Event):
197
210
  await event.reply(i18n.t(\"module.{name}.command.hello.reply\"))
@@ -205,6 +218,11 @@ class Main(BaseModule):
205
218
  async def group_message_handler(event: Event):
206
219
  pass
207
220
 
221
+ # 消息装饰器支持 pattern(glob 通配符)/ regex(正则),不匹配则不触发
222
+ @message.on_message(pattern=\"签到*\")
223
+ async def signin_handler(event: Event):
224
+ await event.reply(\"签到成功\")
225
+
208
226
  @notice.on_friend_add()
209
227
  async def friend_add_handler(event: Event):
210
228
  self.logger.info((\"{text[module.log.friend_add]}\").format(nickname=event.get_user_nickname()))
@@ -227,6 +245,13 @@ dependencies = [
227
245
 
228
246
  [project.entry-points]
229
247
  "erispulse.adapter" = {{ "{entry_key}" = "{name}:{name}" }}
248
+
249
+ [build-system]
250
+ requires = ["hatchling"]
251
+ build-backend = "hatchling.build"
252
+
253
+ [tool.hatch.build.targets.wheel]
254
+ packages = ["{name}"]
230
255
  """
231
256
 
232
257
  _ADAPTER_INIT = """from .Core import {name}
@@ -44,20 +44,14 @@ class InitCommand(Command):
44
44
 
45
45
  def add_arguments(self, parser: ArgumentParser):
46
46
  parser.add_argument("--project-name", "-n", help=i18n.t("cli.init.name_help"))
47
- parser.add_argument(
48
- "--quick", "-q", action="store_true", help=i18n.t("cli.init.quick_help")
49
- )
50
- parser.add_argument(
51
- "--force", "-f", action="store_true", help=i18n.t("cli.init.force_help")
52
- )
47
+ parser.add_argument("--quick", "-q", action="store_true", help=i18n.t("cli.init.quick_help"))
48
+ parser.add_argument("--force", "-f", action="store_true", help=i18n.t("cli.init.force_help"))
53
49
  parser.add_argument(
54
50
  "--here",
55
51
  action="store_true",
56
52
  help=i18n.t("cli.init.here_help"),
57
53
  )
58
- parser.add_argument(
59
- "--no-uv", action="store_true", help=i18n.t("cli.init.nouv_help")
60
- )
54
+ parser.add_argument("--no-uv", action="store_true", help=i18n.t("cli.init.nouv_help"))
61
55
 
62
56
  def execute(self, args):
63
57
  self.no_uv = getattr(args, "no_uv", False)
@@ -107,19 +101,13 @@ class InitCommand(Command):
107
101
 
108
102
  if project_path.exists():
109
103
  if project_path.is_dir():
110
- console.print(
111
- f"[warning] {i18n.t('cli.init.dir_exists', name=project_name)}[/]"
112
- )
104
+ console.print(f"[warning] {i18n.t('cli.init.dir_exists', name=project_name)}[/]")
113
105
  else:
114
- console.print(
115
- f"[error] {i18n.t('cli.init.file_exists_not_dir', name=project_name)}[/]"
116
- )
106
+ console.print(f"[error] {i18n.t('cli.init.file_exists_not_dir', name=project_name)}[/]")
117
107
  return False
118
108
  else:
119
109
  project_path.mkdir()
120
- console.print(
121
- f"[success] {i18n.t('cli.init.created_dir', name=project_name)}[/]"
122
- )
110
+ console.print(f"[success] {i18n.t('cli.init.created_dir', name=project_name)}[/]")
123
111
 
124
112
  try:
125
113
  for dir_name in ["config", "logs"]:
@@ -131,8 +119,7 @@ class InitCommand(Command):
131
119
  ssl_file = ssl_dir / ssl_name
132
120
  if not ssl_file.exists():
133
121
  ssl_file.write_text(
134
- "# 将你的证书/密钥 PEM 内容粘贴到本文件,"
135
- "或在配置中改用 ssl_cert/ssl_key 内联填写\n",
122
+ "# 将你的证书/密钥 PEM 内容粘贴到本文件,或在配置中改用 ssl_cert/ssl_key 内联填写\n",
136
123
  encoding="utf-8",
137
124
  )
138
125
 
@@ -168,29 +155,19 @@ class InitCommand(Command):
168
155
  f.write('if __name__ == "__main__":\n')
169
156
  f.write(" asyncio.run(main())\n")
170
157
 
171
- console.print(
172
- f"[success] {i18n.t('cli.init.display_success', name=display_name)}[/]"
173
- )
158
+ console.print(f"[success] {i18n.t('cli.init.display_success', name=display_name)}[/]")
174
159
  console.print()
175
160
  console.print(Text(i18n.t("cli.create.next_steps"), style="bold"))
176
161
  if in_current_dir:
177
- console.print(
178
- f" · {i18n.t('cli.init.edit_config', path='config/config.toml')}"
179
- )
162
+ console.print(f" · {i18n.t('cli.init.edit_config', path='config/config.toml')}")
180
163
  console.print(f" · {i18n.t('cli.init.run_direct')}")
181
164
  else:
182
- console.print(
183
- f" · {i18n.t('cli.init.edit_config', path=f'{display_name}/config/config.toml')}"
184
- )
185
- console.print(
186
- f" · {i18n.t('cli.init.cd_and_run', dir=display_name)}"
187
- )
165
+ console.print(f" · {i18n.t('cli.init.edit_config', path=f'{display_name}/config/config.toml')}")
166
+ console.print(f" · {i18n.t('cli.init.cd_and_run', dir=display_name)}")
188
167
  return True
189
168
 
190
169
  except Exception as e:
191
- console.print(
192
- f"[error] {i18n.t('cli.init.init_project_failed', error=e)}[/]"
193
- )
170
+ console.print(f"[error] {i18n.t('cli.init.init_project_failed', error=e)}[/]")
194
171
  return False
195
172
 
196
173
  @staticmethod
@@ -335,9 +312,7 @@ class InitCommand(Command):
335
312
  if adapters:
336
313
  return adapters
337
314
  except Exception as e:
338
- console.print(
339
- f"[warning] {i18n.t('cli.init.fetch_remote_failed', error=e)}[/]"
340
- )
315
+ console.print(f"[warning] {i18n.t('cli.init.fetch_remote_failed', error=e)}[/]")
341
316
 
342
317
  return {
343
318
  "yunhu": i18n.t("cli.init.adapter_desc_yunhu"),
@@ -346,9 +321,7 @@ class InitCommand(Command):
346
321
  "email": i18n.t("cli.init.adapter_desc_email"),
347
322
  }
348
323
 
349
- def _interactive_init(
350
- self, project_name: str | None = None, force: bool = False, here: bool = False
351
- ) -> bool:
324
+ def _interactive_init(self, project_name: str | None = None, force: bool = False, here: bool = False) -> bool:
352
325
  """
353
326
  交互式初始化项目,引导用户配置项目位置及基本参数
354
327
 
@@ -368,9 +341,7 @@ class InitCommand(Command):
368
341
  f" [bold]2.[/] {i18n.t('cli.init.location_option_new')} [dim]— {i18n.t('cli.init.location_desc_new')}[/]"
369
342
  )
370
343
  console.print()
371
- location_choice = IntPrompt.ask(
372
- i18n.t("cli.create.select_prompt"), default=2, choices=["1", "2"]
373
- )
344
+ location_choice = IntPrompt.ask(i18n.t("cli.create.select_prompt"), default=2, choices=["1", "2"])
374
345
  console.print()
375
346
  in_current_dir = location_choice == 1
376
347
 
@@ -423,26 +394,20 @@ class InitCommand(Command):
423
394
  config.setConfig("ErisPulse.logger.level", new_level.upper())
424
395
 
425
396
  current_host = config.getConfig("ErisPulse.server.host", "0.0.0.0")
426
- console.print(
427
- f" {i18n.t('cli.init.listen_host')} [dim]({current_host})[/]"
428
- )
397
+ console.print(f" {i18n.t('cli.init.listen_host')} [dim]({current_host})[/]")
429
398
  new_host = _input(">")
430
399
  if new_host:
431
400
  config.setConfig("ErisPulse.server.host", new_host)
432
401
 
433
402
  current_port = str(config.getConfig("ErisPulse.server.port", 8000))
434
- console.print(
435
- f" {i18n.t('cli.init.listen_port')} [dim]({current_port})[/]"
436
- )
403
+ console.print(f" {i18n.t('cli.init.listen_port')} [dim]({current_port})[/]")
437
404
  new_port = _input(">")
438
405
  while new_port:
439
406
  try:
440
407
  config.setConfig("ErisPulse.server.port", int(new_port))
441
408
  break
442
409
  except ValueError:
443
- console.print(
444
- f"[warning] {i18n.t('cli.init.invalid_port', port=new_port)}[/]"
445
- )
410
+ console.print(f"[warning] {i18n.t('cli.init.invalid_port', port=new_port)}[/]")
446
411
  new_port = _input(">")
447
412
 
448
413
  if Confirm.ask(
@@ -466,19 +431,13 @@ class InitCommand(Command):
466
431
  """
467
432
  from ErisPulse import config
468
433
 
469
- with console.status(
470
- f"[bold green]{i18n.t('cli.init.fetching_adapters')}...", spinner="dots"
471
- ):
434
+ with console.status(f"[bold green]{i18n.t('cli.init.fetching_adapters')}...", spinner="dots"):
472
435
  try:
473
436
  with concurrent.futures.ThreadPoolExecutor() as executor:
474
- future = executor.submit(
475
- asyncio.run, self._fetch_available_adapters()
476
- )
437
+ future = executor.submit(asyncio.run, self._fetch_available_adapters())
477
438
  adapters = future.result(timeout=10)
478
439
  except Exception as e:
479
- console.print(
480
- f"[error] {i18n.t('cli.init.fetch_adapters_failed', error=e)}[/]"
481
- )
440
+ console.print(f"[error] {i18n.t('cli.init.fetch_adapters_failed', error=e)}[/]")
482
441
  return
483
442
 
484
443
  if not adapters:
@@ -510,30 +469,30 @@ class InitCommand(Command):
510
469
  enabled.append(name)
511
470
  config.setConfig(f"ErisPulse.adapters.status.{name}", True)
512
471
  else:
513
- console.print(
514
- f"[warning] {i18n.t('cli.init.invalid_index', idx=idx)}[/]"
515
- )
472
+ console.print(f"[warning] {i18n.t('cli.init.invalid_index', idx=idx)}[/]")
516
473
 
517
474
  for name, _ in adapter_list:
518
475
  if name not in enabled:
519
476
  config.setConfig(f"ErisPulse.adapters.status.{name}", False)
520
477
 
521
- console.print(
522
- f"[dim] {i18n.t('cli.init.adapters_enabled', count=len(enabled))}[/]"
523
- )
478
+ console.print(f"[dim] {i18n.t('cli.init.adapters_enabled', count=len(enabled))}[/]")
524
479
 
525
- if enabled and Confirm.ask(
526
- f" [cyan]{i18n.t('cli.init.install_selected_prompt')}[/]", default=True
527
- ):
480
+ if enabled and Confirm.ask(f" [cyan]{i18n.t('cli.init.install_selected_prompt')}[/]", default=True):
528
481
  self._install_adapters(enabled, adapters)
529
482
 
530
483
  def _install_adapters(self, adapter_names, adapters_info):
531
484
  """
532
485
  安装选中的适配器
533
486
 
487
+ 安装成功后衔接交互式配置向导(写入新项目的 config.toml)
488
+
534
489
  :param adapter_names: [list] 适配器简称列表
535
490
  :param adapters_info: [dict] 适配器信息
536
491
  """
492
+ from ErisPulse import config
493
+
494
+ from ..utils import config_wizard
495
+
537
496
  pkg_manager = PackageManager()
538
497
  pkg_manager.no_uv = getattr(self, "no_uv", False)
539
498
  for adapter_name in adapter_names:
@@ -542,14 +501,10 @@ class InitCommand(Command):
542
501
  remote_packages = pkg_manager._cache.get("remote_packages", {})
543
502
  if not remote_packages:
544
503
  with concurrent.futures.ThreadPoolExecutor() as executor:
545
- future = executor.submit(
546
- asyncio.run, pkg_manager.get_remote_packages()
547
- )
504
+ future = executor.submit(asyncio.run, pkg_manager.get_remote_packages())
548
505
  remote_packages = future.result(timeout=10)
549
506
  if adapter_name in remote_packages.get("adapters", {}):
550
- package_name = remote_packages["adapters"][adapter_name].get(
551
- "package"
552
- )
507
+ package_name = remote_packages["adapters"][adapter_name].get("package")
553
508
  except Exception:
554
509
  pass
555
510
 
@@ -562,6 +517,10 @@ class InitCommand(Command):
562
517
  success = pkg_manager.install_package([package_name])
563
518
 
564
519
  if not success:
565
- console.print(
566
- f"[error] {i18n.t('cli.init.adapter_install_failed', name=adapter_name)}[/]"
567
- )
520
+ console.print(f"[error] {i18n.t('cli.init.adapter_install_failed', name=adapter_name)}[/]")
521
+ elif config_wizard.is_interactive():
522
+ # 安装成功后衔接配置向导,写入已重定向到新项目的 config.toml
523
+ try:
524
+ config_wizard.post_install_configure([package_name], config)
525
+ except Exception as e:
526
+ console.print(f"[warning] {i18n.t('cli.config.post_install_failed', error=e)}[/]")