ErisPulse 2.10.0.dev0__tar.gz → 2.10.2__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 (143) hide show
  1. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/PKG-INFO +4 -3
  2. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/pyproject.toml +9 -3
  3. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/base.py +3 -1
  4. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/cli.py +62 -3
  5. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/config.py +1 -0
  6. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/create.py +309 -24
  7. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/doctor.py +127 -2
  8. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/init.py +75 -13
  9. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/install.py +1 -0
  10. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/language.py +1 -0
  11. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/list.py +1 -0
  12. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/list_remote.py +1 -0
  13. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/run.py +144 -9
  14. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/self_update.py +1 -0
  15. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/types.py +1 -0
  16. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/uninstall.py +1 -0
  17. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/upgrade.py +1 -0
  18. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/version.py +1 -0
  19. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/constants.py +1 -1
  20. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/i18n/locales/en.py +39 -2
  21. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/i18n/locales/ja.py +39 -2
  22. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/i18n/locales/ru.py +39 -2
  23. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +39 -2
  24. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +39 -2
  25. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/utils/config_wizard.py +7 -2
  26. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/utils/package_manager.py +7 -6
  27. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/utils/scaffold_text.py +18 -0
  28. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/adapter.py +1 -1
  29. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/config_schema.py +68 -7
  30. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/converter.py +11 -2
  31. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/module.py +3 -1
  32. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/base.py +28 -4
  33. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/command.py +104 -50
  34. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/interaction.py +48 -16
  35. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/message.py +12 -4
  36. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/message_builder.py +7 -1
  37. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/overrides.py +7 -3
  38. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/wrapper.py +116 -33
  39. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/assets/error.css +20 -10
  40. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/assets/root.css +23 -13
  41. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/config.py +282 -104
  42. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/constants.py +15 -0
  43. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/i18n/locales/en.py +10 -0
  44. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/i18n/locales/ja.py +10 -0
  45. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/i18n/locales/ru.py +10 -0
  46. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/i18n/locales/zh_cn.py +10 -0
  47. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/i18n/locales/zh_tw.py +10 -0
  48. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/module.py +97 -13
  49. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/router.py +41 -20
  50. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/loaders/adapter.py +81 -3
  51. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/loaders/plugin_folder.py +203 -7
  52. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/example_config.py +5 -1
  53. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/sdk_initializer.py +17 -0
  54. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/.gitignore +0 -0
  55. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/LICENSE +0 -0
  56. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/README.pypi.md +0 -0
  57. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/__init__.py +0 -0
  58. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/commands/__init__.py +0 -0
  59. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/console.py +0 -0
  60. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/hints.py +0 -0
  61. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
  62. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
  63. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/registry.py +0 -0
  64. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/utils/__init__.py +0 -0
  65. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/utils/display.py +0 -0
  66. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/CLI/utils/interactive.py +0 -0
  67. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/__init__.py +0 -0
  68. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/api_dsl.py +0 -0
  69. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/client.py +0 -0
  70. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/errors.py +0 -0
  71. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
  72. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/kv_builder.py +0 -0
  73. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/manager.py +0 -0
  74. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/model.py +0 -0
  75. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/router.py +0 -0
  76. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
  77. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
  78. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/sql_base.py +0 -0
  79. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/storage.py +0 -0
  80. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Bases/websocket.py +0 -0
  81. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/__init__.py +0 -0
  82. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/command_args.py +0 -0
  83. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/conversation.py +0 -0
  84. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/governance.py +0 -0
  85. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/meta.py +0 -0
  86. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/notice.py +0 -0
  87. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/request.py +0 -0
  88. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/session_type.py +0 -0
  89. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/throttle.py +0 -0
  90. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/Event/trace.py +0 -0
  91. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/__init__.py +0 -0
  92. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/adapter.py +0 -0
  93. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/assets/__init__.py +0 -0
  94. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/assets/error.html +0 -0
  95. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/assets/root.html +0 -0
  96. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/client.py +0 -0
  97. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/connections.py +0 -0
  98. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/di.py +0 -0
  99. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/i18n/__init__.py +0 -0
  100. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/i18n/constants.py +0 -0
  101. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
  102. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/lifecycle.py +0 -0
  103. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/logger.py +0 -0
  104. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/master.py +0 -0
  105. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/ownership.py +0 -0
  106. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/scope.py +0 -0
  107. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/shadow.py +0 -0
  108. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/storage/__init__.py +0 -0
  109. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/storage/mysql.py +0 -0
  110. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/storage/postgres.py +0 -0
  111. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/storage/sqlite.py +0 -0
  112. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/text_match.py +0 -0
  113. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/Core/transcript.py +0 -0
  114. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/__init__.py +0 -0
  115. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/__main__.py +0 -0
  116. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/finders/__init__.py +0 -0
  117. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/finders/adapter.py +0 -0
  118. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/finders/bases/__init__.py +0 -0
  119. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/finders/bases/finder.py +0 -0
  120. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/finders/module.py +0 -0
  121. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/loaders/__init__.py +0 -0
  122. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/loaders/bases/__init__.py +0 -0
  123. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/loaders/bases/loader.py +0 -0
  124. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/loaders/module.py +0 -0
  125. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/loaders/strategy.py +0 -0
  126. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/loaders/strict.py +0 -0
  127. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/py.typed +0 -0
  128. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/__init__.py +0 -0
  129. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/config_schema.py +0 -0
  130. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/context.py +0 -0
  131. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/diagnostics.py +0 -0
  132. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/exceptions.py +0 -0
  133. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/file_watcher.py +0 -0
  134. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/frame_config.py +0 -0
  135. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/hints.py +0 -0
  136. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/memory.py +0 -0
  137. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/owner_cleanup.py +0 -0
  138. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/plugin_reload.py +0 -0
  139. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/proactive_gc.py +0 -0
  140. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/tasks.py +0 -0
  141. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/troubleshoot.py +0 -0
  142. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/runtime/version.py +0 -0
  143. {erispulse-2.10.0.dev0 → erispulse-2.10.2}/src/ErisPulse/sdk.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ErisPulse
3
- Version: 2.10.0-dev.0
3
+ Version: 2.10.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
@@ -33,8 +33,8 @@ Requires-Dist: aiohttp>=3.9.0
33
33
  Requires-Dist: aiosqlite>=0.20.0
34
34
  Requires-Dist: fastapi>=0.116.1
35
35
  Requires-Dist: rich>=13.0.0
36
- Requires-Dist: tomlkit>=0.15.1
37
- Requires-Dist: uvicorn>=0.35.0
36
+ Requires-Dist: tomlclass<0.4,>=0.3.2
37
+ Requires-Dist: uvicorn<1.0,>=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'
@@ -49,6 +49,7 @@ Requires-Dist: pytest-mock>=3.10.0; extra == 'test'
49
49
  Requires-Dist: pytest-timeout>=2.1.0; extra == 'test'
50
50
  Requires-Dist: pytest-xdist>=3.0.0; extra == 'test'
51
51
  Requires-Dist: pytest>=7.0.0; extra == 'test'
52
+ Requires-Dist: tomli>=2.0.0; (python_version < '3.11') and extra == 'test'
52
53
  Description-Content-Type: text/markdown
53
54
 
54
55
  # ErisPulse
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ErisPulse"
7
- version = "2.10.0-dev.0"
7
+ version = "2.10.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"
@@ -62,12 +62,17 @@ classifiers = [
62
62
 
63
63
  dependencies = [
64
64
  "rich>=13.0.0",
65
- "tomlkit>=0.15.1",
65
+ "tomlclass>=0.3.2,<0.4",
66
+
66
67
  # network 相关
67
68
  "websockets>=12.0",
68
69
  "aiohttp>=3.9.0",
69
70
  "fastapi>=0.116.1",
70
- "uvicorn>=0.35.0",
71
+
72
+ # uvicorn的内嵌启动依赖私有接口 Server._serve()(0.34.0 引入,用于跳过 serve() 的信号捕获),
73
+ # 上限锁定 0.x 线;启动期按能力探测,接口缺失时仅告警并回退公开 serve()
74
+ "uvicorn>=0.35.0,<1.0",
75
+
71
76
  # storage 相关(默认 SQLite 后端异步驱动,开箱即用)
72
77
  "aiosqlite>=0.20.0",
73
78
  ]
@@ -98,6 +103,7 @@ test = [
98
103
  "pytest-timeout>=2.1.0",
99
104
  "pytest-mock>=3.10.0",
100
105
  "httpx>=0.24.0",
106
+ "tomli>=2.0.0; python_version < '3.11'",
101
107
  ]
102
108
 
103
109
  [dependency-groups]
@@ -18,13 +18,15 @@ class Command(ABC):
18
18
  1. 每个命令类必须实现 add_arguments 和 execute 方法
19
19
  2. name 和 description 为类属性,必须在子类中定义
20
20
  3. aliases 为可选类属性,用于定义命令的简化形式
21
- 4. execute 方法接收解析后的 args 对象
21
+ 4. category 为可选类属性,决定主帮助分组表中的归类
22
+ 5. execute 方法接收解析后的 args 对象
22
23
  {!--< /tips >!--}
23
24
  """
24
25
 
25
26
  name: str = "" # 命令名称
26
27
  description: str = "" # 命令描述
27
28
  aliases: list = [] # 命令别名(简化形式,如 "install" -> ["i", "add"])
29
+ category: str = "" # 主帮助分组类别标识(project/components/config/misc;空串归入 misc)
28
30
 
29
31
  @abstractmethod
30
32
  def add_arguments(self, parser: ArgumentParser):
@@ -168,6 +168,65 @@ class CLI:
168
168
  )
169
169
  )
170
170
 
171
+ # 主帮助分组表展示顺序(未列出的类别按注册顺序附在末尾)
172
+ _HELP_CATEGORY_ORDER = ("project", "components", "config", "misc")
173
+
174
+ def _print_grouped_help(self) -> None:
175
+ """
176
+ 按类别分组打印主帮助(Rich 表格渲染)
177
+
178
+ 取代 argparse 默认的子命令平铺列表:命令按 category 归类展示
179
+ (名称 + 别名 + 描述),全局选项从 ``parser._actions`` 实时遍历
180
+ 生成避免与实际参数漂移。子命令级 ``--help`` 保持 argparse 原样。
181
+ """
182
+ from rich.table import Table
183
+
184
+ if self.parser.description:
185
+ console.print(f"[bold]{self.parser.description}[/]\n")
186
+ console.print(f"[dim]{self.parser.format_usage().strip()}[/]\n")
187
+
188
+ groups: dict[str, list[Command]] = {}
189
+ for command in self.registry.get_all():
190
+ groups.setdefault(command.category or "misc", []).append(command)
191
+
192
+ ordered = list(self._HELP_CATEGORY_ORDER) + [
193
+ cat for cat in groups if cat not in self._HELP_CATEGORY_ORDER
194
+ ]
195
+ table = Table(show_header=False, box=None, pad_edge=False, expand=True)
196
+ table.add_column(min_width=18)
197
+ table.add_column(ratio=1)
198
+ for cat in ordered:
199
+ commands = groups.get(cat)
200
+ if not commands:
201
+ continue
202
+ table.add_row(
203
+ f"[title]{i18n.t(f'cli.help.category.{cat}')}[/]", ""
204
+ )
205
+ for command in sorted(commands, key=lambda c: c.name):
206
+ aliases = getattr(command, "aliases", None) or []
207
+ name_text = command.name + (
208
+ f" [dim]({', '.join(aliases)})[/]" if aliases else ""
209
+ )
210
+ table.add_row(f" [cmd]{name_text}[/]", command.description)
211
+ console.print(table)
212
+
213
+ option_lines: list[tuple[str, str]] = []
214
+ for action in self.parser._actions:
215
+ if not action.option_strings:
216
+ continue
217
+ option_lines.append(
218
+ (", ".join(action.option_strings), action.help or "")
219
+ )
220
+ if option_lines:
221
+ width = max(len(opts) for opts, _ in option_lines)
222
+ console.print(f"\n[title]{i18n.t('cli.parser.optionals_title')}[/]")
223
+ for opts, help_text in option_lines:
224
+ console.print(f" [cmd]{opts.ljust(width)}[/] {help_text}")
225
+
226
+ console.print(
227
+ f"\n[dim]{i18n.t('cli.run.quickstart.run_help_hint')}[/]"
228
+ )
229
+
171
230
  def _print_quickstart(self) -> None:
172
231
  """
173
232
  打印 Quick Start 面板
@@ -248,7 +307,7 @@ class CLI:
248
307
  console.print(
249
308
  f"[error]{i18n.t('cli.run.unknown_command', command=cmd)}[/]"
250
309
  )
251
- self.parser.print_help()
310
+ self._print_grouped_help()
252
311
  sys.exit(1)
253
312
 
254
313
  def _maybe_show_language_hint(self) -> None:
@@ -336,7 +395,7 @@ class CLI:
336
395
  f"[warning]{i18n.t('cli.run.unknown_args', args=' '.join(unknown))}[/]"
337
396
  )
338
397
  self._print_quickstart()
339
- self.parser.print_help()
398
+ self._print_grouped_help()
340
399
  return
341
400
 
342
401
  # 将别名解析为规范命令名
@@ -373,7 +432,7 @@ class CLI:
373
432
  console.print(
374
433
  f"[error]{i18n.t('cli.run.unknown_command', command=args.command)}[/]"
375
434
  )
376
- self.parser.print_help()
435
+ self._print_grouped_help()
377
436
  sys.exit(1)
378
437
 
379
438
  except KeyboardInterrupt:
@@ -34,6 +34,7 @@ class ConfigCommand(Command):
34
34
  name = "config"
35
35
  description = i18n.t("cli.config.description")
36
36
  aliases = ["cfg", "conf"]
37
+ category = "config"
37
38
 
38
39
  def add_arguments(self, parser: ArgumentParser):
39
40
  parser.add_argument(
@@ -5,6 +5,7 @@ Create 命令实现
5
5
  """
6
6
 
7
7
  import datetime
8
+ import shutil
8
9
  import sys
9
10
  from argparse import ArgumentParser
10
11
  from pathlib import Path
@@ -42,10 +43,10 @@ SOFTWARE.
42
43
  """
43
44
 
44
45
  # 生成模块/适配器模板的最低 SDK 地板(写入模板 min_sdk_version 字段)。
45
- # 模板示例使用 args=/options=(2.9 特性);pip 默认不解析预发布版本,
46
- # 该地板不得高于已发布的正式版本,否则生成的组件会因依赖装不上或
46
+ # 模板示例使用根导入契约(2.10 特性)与 args=/options=(2.9 特性);pip 默认不解析
47
+ # 预发布版本,该地板不得高于已发布的正式版本,否则生成的组件会因依赖装不上或
47
48
  # 加载器按 PEP 440 拒载(与 CLI/constants.py 的 INIT_ERISPULSE_DEPENDENCY 同步升版)。
48
- _TEMPLATE_MIN_SDK_VERSION = "2.9.0"
49
+ _TEMPLATE_MIN_SDK_VERSION = "2.10.0"
49
50
 
50
51
  _MODULE_PYPROJECT = """[project]
51
52
  name = "ErisPulse-{name}"
@@ -76,6 +77,33 @@ packages = ["{name}"]
76
77
  _MODULE_INIT = """from .Core import Main
77
78
  """
78
79
 
80
+ # pack 转换用:entry-point 组与类名按插件类型动态生成
81
+ _PACK_PYPROJECT = """[project]
82
+ name = "{pypi_name}"
83
+ version = "1.0.0"
84
+ description = "{description}"
85
+ readme = "README.md"
86
+ requires-python = ">=3.10"
87
+ license = {{ file = "LICENSE" }}
88
+ authors = [ {{ name = "{author}", email = "{email}" }} ]
89
+
90
+ dependencies = [
91
+ ]
92
+
93
+ [project.urls]
94
+ "homepage" = "{homepage}"
95
+
96
+ [project.entry-points]
97
+ "{group}" = {{ "{entry_key}" = "{name}:{class_name}" }}
98
+
99
+ [build-system]
100
+ requires = ["hatchling"]
101
+ build-backend = "hatchling.build"
102
+
103
+ [tool.hatch.build.targets.wheel]
104
+ packages = ["{name}"]
105
+ """
106
+
79
107
  _MODULE_CORE = """from dataclasses import dataclass, field
80
108
 
81
109
  from ErisPulse import (
@@ -107,12 +135,6 @@ class Main(BaseModule):
107
135
  :ivar advanced_threshold: 高级阈值(docstring :ivar: 声明即为字段描述,无需 metadata)
108
136
  \"\"\"
109
137
 
110
- enabled: bool = field(
111
- default=True,
112
- metadata={{
113
- \"description\": {{\"i18n\": \"module.{name}.enabled\", \"default\": \"Enable module\"}},
114
- }},
115
- )
116
138
  # example 字段:默认不写入 config.toml(仅记录在 config.full.example,用户按需启用)
117
139
  advanced_threshold: int = field(
118
140
  default=100,
@@ -132,15 +154,6 @@ class Main(BaseModule):
132
154
  ru=\"Модуль {name}\",
133
155
  zh_TW=\"{name} 模組\",
134
156
  )
135
- enabled: I18nKey = I18nKey(
136
- key=\"module.{name}.enabled\",
137
- default=\"Enable module\",
138
- zh_CN=\"是否启用模块\",
139
- en=\"Enable module\",
140
- ja=\"モジュールを有効にする\",
141
- ru=\"Включить модуль\",
142
- zh_TW=\"啟用模組\",
143
- )
144
157
  hello_help: I18nKey = I18nKey(
145
158
  key=\"module.{name}.command.hello.help\",
146
159
  default=\"Send a greeting message\",
@@ -222,8 +235,13 @@ class Main(BaseModule):
222
235
  group=\"default\",
223
236
  tags=[\"{name}\"],
224
237
  # 对外服务白名单(可选):声明后其他模块可经 sdk.module.call() 调用这些方法;
225
- # 缺省时公开方法全开放(限制由用户通过 scope.actions 配置)
226
- # services=[\"get_welcome_message\"],
238
+ # 缺省时公开方法全开放(限制由用户通过 scope.actions 配置)。
239
+ # dict 形态可声明 description 与 parameters 参数提示(供 sdk.module.services()
240
+ # 服务目录 / AI 智能体消费);未声明的参数自动取方法 docstring 的 :param: 行
241
+ # services=[
242
+ # "get_welcome_message",
243
+ # {{"name": "render", "description": "渲染", "parameters": {{"html": "HTML 文本"}}}},
244
+ # ],
227
245
  )
228
246
 
229
247
  @staticmethod
@@ -576,6 +594,7 @@ class {name}(BaseAdapter):
576
594
  return self.make_error(message=str(e))
577
595
  """
578
596
 
597
+
579
598
  _ADAPTER_CONVERTER = """\"\"\"
580
599
  {text[adapter.converter_doc]}
581
600
  \"\"\"
@@ -775,12 +794,13 @@ class CreateCommand(Command):
775
794
  name = "create"
776
795
  description = i18n.t("cli.create.description")
777
796
  aliases = ["c", "new"]
797
+ category = "project"
778
798
 
779
799
  def add_arguments(self, parser: ArgumentParser):
780
800
  parser.add_argument(
781
801
  "create_type",
782
802
  nargs="?",
783
- choices=["module", "adapter"],
803
+ choices=["module", "adapter", "pack"],
784
804
  help=i18n.t("cli.create.type_help"),
785
805
  )
786
806
  parser.add_argument("--name", "-n", help=i18n.t("cli.create.name_help"))
@@ -806,7 +826,7 @@ class CreateCommand(Command):
806
826
  "--local",
807
827
  action="store_true",
808
828
  default=False,
809
- help=i18n.t("cli.create.local_module_help"),
829
+ help=i18n.t("cli.create.local_help"),
810
830
  )
811
831
 
812
832
  def execute(self, args):
@@ -814,6 +834,11 @@ class CreateCommand(Command):
814
834
  if not create_type:
815
835
  create_type = self._interactive_select_type()
816
836
 
837
+ # pack:本地插件 → 可发布项目(无需组件名,直接进入转换流程)
838
+ if create_type == "pack":
839
+ self._create_pack(args)
840
+ return
841
+
817
842
  default_name = "MyModule" if create_type == "module" else "MyAdapter"
818
843
  name = args.name
819
844
  # 名称校验:非法时保留输入并重新提示,而非直接退出
@@ -843,12 +868,15 @@ class CreateCommand(Command):
843
868
  console.print(
844
869
  f" [bold]2.[/] Adapter [dim]{i18n.t('cli.create.select_type_adapter')}[/]"
845
870
  )
871
+ console.print(
872
+ f" [bold]3.[/] Pack [dim]{i18n.t('cli.create.select_type_pack')}[/]"
873
+ )
846
874
  console.print()
847
875
  choice = IntPrompt.ask(
848
- i18n.t("cli.create.select_prompt"), default=1, choices=["1", "2"]
876
+ i18n.t("cli.create.select_prompt"), default=1, choices=["1", "2", "3"]
849
877
  )
850
878
  console.print()
851
- return "module" if choice == 1 else "adapter"
879
+ return ("module", "adapter", "pack")[choice - 1]
852
880
 
853
881
  def _ask_missing(
854
882
  self, args, field_name: str, prompt_text: str, default: str = ""
@@ -867,6 +895,184 @@ class CreateCommand(Command):
867
895
  val = Prompt.ask(f" {prompt_text}", default=default)
868
896
  return val
869
897
 
898
+ def _create_pack(self, args):
899
+ """
900
+ 将本地插件(plugins/ 目录)转换为可发布的标准项目结构
901
+
902
+ 交互式选择插件目录中的插件,识别类型(模块/适配器)后输入包元信息,
903
+ 输出含 pyproject.toml(entry-point 按插件类型生成)与插件源码的完整
904
+ 项目目录,可直接 ``pip install -e .`` 或打包发布。
905
+
906
+ :param args: [Any] 解析后的命令参数对象
907
+ :return: [None] 无返回值
908
+ """
909
+ from ErisPulse.loaders.plugin_folder import PluginFolderLoader
910
+
911
+ # 1. 收集插件目录中的候选插件
912
+ loader = PluginFolderLoader()
913
+ candidates: list[tuple[str, Path]] = []
914
+ for base in loader.get_plugins_dirs():
915
+ if not base.is_dir():
916
+ continue
917
+ for entry in sorted(base.iterdir()):
918
+ plugin_name = PluginFolderLoader._plugin_name_of(entry)
919
+ if plugin_name is not None:
920
+ candidates.append((plugin_name, entry))
921
+
922
+ if not candidates:
923
+ console.print(f"[error] {i18n.t('cli.create.pack_no_plugins')}")
924
+ sys.exit(1)
925
+
926
+ # 2. 选择插件:--name 直选,否则交互编号选择
927
+ plugin_name = getattr(args, "name", None)
928
+ selected = next((c for c in candidates if c[0] == plugin_name), None)
929
+ if selected is None:
930
+ if plugin_name:
931
+ console.print(
932
+ f"[error] {i18n.t('cli.create.pack_not_found', name=plugin_name)}"
933
+ )
934
+ sys.exit(1)
935
+ console.print(Text(i18n.t("cli.create.pack_select_title"), style="bold"))
936
+ for i, (name, path) in enumerate(candidates, start=1):
937
+ console.print(f" [bold]{i}.[/] {name} [dim]{path}[/]")
938
+ console.print()
939
+ choice = IntPrompt.ask(
940
+ i18n.t("cli.create.pack_select_prompt"),
941
+ default=1,
942
+ choices=[str(i) for i in range(1, len(candidates) + 1)],
943
+ )
944
+ selected = candidates[choice - 1]
945
+ console.print()
946
+
947
+ plugin_name, plugin_path = selected
948
+
949
+ # 3. 导入插件并识别类型
950
+ try:
951
+ module_obj = loader._import_plugin(plugin_name, plugin_path)
952
+ except Exception as e:
953
+ console.print(f"[error] {i18n.t('cli.create.failed', error=e)}")
954
+ sys.exit(1)
955
+ module_class = PluginFolderLoader._find_module_class(module_obj)
956
+ adapter_class = PluginFolderLoader._find_adapter_class(module_obj)
957
+ if module_class is not None:
958
+ class_name = module_class.__name__
959
+ group = "erispulse.module"
960
+ kind_label = "module"
961
+ elif adapter_class is not None:
962
+ class_name = adapter_class.__name__
963
+ group = "erispulse.adapter"
964
+ kind_label = "adapter"
965
+ else:
966
+ console.print(
967
+ f"[error] {i18n.t('cli.create.pack_unknown_kind', name=plugin_name)}"
968
+ )
969
+ sys.exit(1)
970
+ console.print(
971
+ f"[success] {i18n.t('cli.create.pack_detected', name=plugin_name, kind=kind_label, cls=class_name)}"
972
+ )
973
+
974
+ # 4. 元信息(项目名默认插件名,经组件名校验)
975
+ project_name = prompt_validated(
976
+ i18n.t("cli.create.pack_project_prompt"),
977
+ default=plugin_name,
978
+ validate=_validate_name,
979
+ error_msg=i18n.t("cli.create.name_error"),
980
+ )
981
+ description = self._ask_missing(
982
+ args,
983
+ "description",
984
+ i18n.t("cli.create.pack_desc_prompt"),
985
+ i18n.t("cli.create.pack_desc_placeholder", name=project_name),
986
+ )
987
+ author = self._ask_missing(args, "author", i18n.t("cli.create.author_prompt"), "yourname")
988
+ email = self._ask_missing(args, "email", i18n.t("cli.create.email_prompt"), "your@mail.com")
989
+ homepage = self._ask_missing(
990
+ args,
991
+ "homepage",
992
+ i18n.t("cli.create.homepage_prompt"),
993
+ f"https://github.com/{author}/{project_name}",
994
+ )
995
+ entry_key = _camel_to_snake(project_name)
996
+
997
+ output = Path(args.output)
998
+ project_dir = output / project_name
999
+ if project_dir.exists() and not args.force:
1000
+ console.print(
1001
+ f"[error] {i18n.t('cli.create.dir_exists', dir=str(project_dir))}"
1002
+ )
1003
+ sys.exit(1)
1004
+
1005
+ # 5. 生成项目:源码拷贝 + pyproject(entry-point 按类型)+ LICENSE/README
1006
+ try:
1007
+ pkg_dir = project_dir / project_name
1008
+ pkg_dir.mkdir(parents=True, exist_ok=True)
1009
+
1010
+ if plugin_path.is_file():
1011
+ # 单文件插件 → 包内模块 + __init__ 导出
1012
+ (pkg_dir / "__init__.py").write_text(
1013
+ f"from .{plugin_path.stem} import {class_name}\n",
1014
+ encoding="utf-8",
1015
+ )
1016
+ shutil.copy2(plugin_path, pkg_dir / plugin_path.name)
1017
+ else:
1018
+ shutil.copytree(
1019
+ plugin_path,
1020
+ pkg_dir,
1021
+ dirs_exist_ok=True,
1022
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
1023
+ )
1024
+
1025
+ (project_dir / "pyproject.toml").write_text(
1026
+ _PACK_PYPROJECT.format(
1027
+ name=project_name,
1028
+ pypi_name=f"ErisPulse-{project_name}",
1029
+ description=description,
1030
+ author=author,
1031
+ email=email,
1032
+ homepage=homepage,
1033
+ group=group,
1034
+ entry_key=entry_key,
1035
+ class_name=class_name,
1036
+ ),
1037
+ encoding="utf-8",
1038
+ )
1039
+ (project_dir / "LICENSE").write_text(
1040
+ _LICENSE_TEMPLATE.format(
1041
+ year=str(datetime.datetime.now().year), author=author
1042
+ ),
1043
+ encoding="utf-8",
1044
+ )
1045
+ readme_tpl = _README_MODULE if module_class is not None else _README_ADAPTER
1046
+ readme_kwargs = {"name": project_name, "description": description}
1047
+ if module_class is None:
1048
+ readme_kwargs["name_snake"] = entry_key
1049
+ (project_dir / "README.md").write_text(
1050
+ readme_tpl.format(**readme_kwargs),
1051
+ encoding="utf-8",
1052
+ )
1053
+
1054
+ console.print()
1055
+ console.print(
1056
+ f"[success] {i18n.t('cli.create.pack_created', name=plugin_name, dir=str(project_dir))}[/]"
1057
+ )
1058
+ console.print()
1059
+ console.print(Text(i18n.t("cli.create.project_structure"), style="bold"))
1060
+ console.print(f" {project_name}/")
1061
+ console.print(" ├── pyproject.toml")
1062
+ console.print(" ├── LICENSE")
1063
+ console.print(" ├── README.md")
1064
+ console.print(f" └── {project_name}/ [dim]({plugin_path})[/]")
1065
+ console.print()
1066
+ console.print(Text(i18n.t("cli.create.next_steps"), style="bold"))
1067
+ console.print(f" · {i18n.t('cli.create.cd_to', dir=project_name)}")
1068
+ console.print(f" · {i18n.t('cli.create.install_dev')}")
1069
+ console.print(f" · {i18n.t('cli.create.run_test')}")
1070
+ console.print(f" · {i18n.t('cli.create.pack_remove_hint', name=plugin_name)}")
1071
+
1072
+ except Exception as e:
1073
+ console.print(f"[error] {i18n.t('cli.create.failed', error=e)}")
1074
+ sys.exit(1)
1075
+
870
1076
  def _create_module(self, args, name: str):
871
1077
  """
872
1078
  创建 Module 项目脚手架
@@ -1014,6 +1220,80 @@ class CreateCommand(Command):
1014
1220
  console.print(f"[error] {i18n.t('cli.create.failed', error=e)}")
1015
1221
  sys.exit(1)
1016
1222
 
1223
+ def _create_adapter_local(self, args, name: str):
1224
+ """
1225
+ 创建本地适配器插件(--local):生成 plugins/ 目录结构,免打包安装
1226
+
1227
+ :param args: [Any] 解析后的命令参数对象
1228
+ :param name: [str] 适配器名称
1229
+ :return: [None] 无返回值
1230
+ """
1231
+ output = Path(args.output)
1232
+ plugins_dir = output / "plugins"
1233
+ plugin_dir = plugins_dir / name
1234
+
1235
+ if plugin_dir.exists() and not args.force:
1236
+ console.print(
1237
+ f"[error] {i18n.t('cli.create.dir_exists', dir=str(plugin_dir))}"
1238
+ )
1239
+ sys.exit(1)
1240
+
1241
+ try:
1242
+ plugin_dir.mkdir(parents=True, exist_ok=True)
1243
+
1244
+ converter_name = _to_converter_name(name)
1245
+ entry_key = _camel_to_snake(name)
1246
+
1247
+ (plugin_dir / "__init__.py").write_text(
1248
+ _ADAPTER_INIT.format(name=name, converter_name=converter_name),
1249
+ encoding="utf-8",
1250
+ )
1251
+ (plugin_dir / "Core.py").write_text(
1252
+ _ADAPTER_CORE.format(
1253
+ name=name,
1254
+ converter_name=converter_name,
1255
+ entry_key=entry_key,
1256
+ text=_scaffold_text(name),
1257
+ min_sdk=_TEMPLATE_MIN_SDK_VERSION,
1258
+ ),
1259
+ encoding="utf-8",
1260
+ )
1261
+ (plugin_dir / "Converter.py").write_text(
1262
+ _ADAPTER_CONVERTER.format(
1263
+ name=name,
1264
+ converter_name=converter_name,
1265
+ entry_key=entry_key,
1266
+ text=_scaffold_text(name),
1267
+ ),
1268
+ encoding="utf-8",
1269
+ )
1270
+
1271
+ console.print()
1272
+ console.print(
1273
+ f"[success] {i18n.t('cli.create.adapter_created', name=name)}[/]"
1274
+ )
1275
+ console.print()
1276
+ console.print(Text(i18n.t("cli.create.local_structure"), style="bold"))
1277
+ console.print(" plugins/")
1278
+ console.print(f" └── {name}/")
1279
+ console.print(" ├── __init__.py")
1280
+ console.print(" ├── Core.py")
1281
+ console.print(" └── Converter.py")
1282
+ console.print()
1283
+ console.print(Text(i18n.t("cli.create.next_steps"), style="bold"))
1284
+ console.print(f" · {i18n.t('cli.create.cd_to', dir=str(plugin_dir))}")
1285
+ console.print(
1286
+ f" · {i18n.t('cli.create.edit_adapter', file=str(plugin_dir) + '/Core.py')}"
1287
+ )
1288
+ console.print(
1289
+ f" · {i18n.t('cli.create.edit_converter', file=str(plugin_dir) + '/Converter.py')}"
1290
+ )
1291
+ console.print(f" · {i18n.t('cli.create.local_tip')}")
1292
+
1293
+ except Exception as e:
1294
+ console.print(f"[error] {i18n.t('cli.create.failed', error=e)}")
1295
+ sys.exit(1)
1296
+
1017
1297
  def _create_adapter(self, args, name: str):
1018
1298
  """
1019
1299
  创建 Adapter 项目脚手架
@@ -1022,6 +1302,11 @@ class CreateCommand(Command):
1022
1302
  :param name: [str] 适配器名称
1023
1303
  :return: [None] 无返回值
1024
1304
  """
1305
+ # --local:创建本地适配器插件(plugins/ 目录结构,免打包安装)
1306
+ if getattr(args, "local", False):
1307
+ self._create_adapter_local(args, name)
1308
+ return
1309
+
1025
1310
  description = self._ask_missing(
1026
1311
  args,
1027
1312
  "description",