ErisPulse 2.10.1__tar.gz → 2.11.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 (147) hide show
  1. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/PKG-INFO +3 -2
  2. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/pyproject.toml +4 -2
  3. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/base.py +3 -1
  4. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/cli.py +62 -3
  5. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/config.py +1 -0
  6. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/create.py +306 -6
  7. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/doctor.py +127 -2
  8. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/init.py +86 -13
  9. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/install.py +1 -0
  10. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/language.py +1 -0
  11. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/list.py +1 -0
  12. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/list_remote.py +1 -0
  13. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/run.py +185 -31
  14. erispulse-2.11.0.dev0/src/ErisPulse/CLI/commands/runtime.py +153 -0
  15. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/self_update.py +1 -0
  16. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/types.py +1 -0
  17. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/uninstall.py +1 -0
  18. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/upgrade.py +1 -0
  19. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/version.py +1 -0
  20. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/i18n/locales/en.py +50 -2
  21. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/i18n/locales/ja.py +50 -2
  22. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/i18n/locales/ru.py +50 -2
  23. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +50 -2
  24. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +50 -2
  25. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/utils/config_wizard.py +7 -2
  26. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/utils/package_manager.py +7 -6
  27. erispulse-2.11.0.dev0/src/ErisPulse/CLI/utils/runtime_state.py +208 -0
  28. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/utils/scaffold_text.py +18 -0
  29. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/adapter.py +1 -1
  30. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/config_schema.py +68 -7
  31. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/module.py +3 -1
  32. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/__init__.py +2 -0
  33. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/base.py +8 -0
  34. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/command.py +17 -1
  35. erispulse-2.11.0.dev0/src/ErisPulse/Core/Event/command_flow.py +348 -0
  36. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/wrapper.py +5 -1
  37. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/adapter.py +49 -0
  38. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/assets/error.css +20 -10
  39. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/assets/root.css +23 -13
  40. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/config.py +299 -101
  41. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/constants.py +48 -4
  42. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/i18n/locales/en.py +29 -11
  43. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/i18n/locales/ja.py +29 -11
  44. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/i18n/locales/ru.py +29 -11
  45. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/i18n/locales/zh_cn.py +29 -11
  46. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/i18n/locales/zh_tw.py +29 -11
  47. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/lifecycle.py +2 -1
  48. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/module.py +541 -36
  49. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/router.py +0 -16
  50. erispulse-2.11.0.dev0/src/ErisPulse/loaders/adapter.py +907 -0
  51. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/loaders/module.py +378 -37
  52. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/loaders/plugin_folder.py +203 -7
  53. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/example_config.py +5 -1
  54. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/sdk_initializer.py +4 -2
  55. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/sdk.py +102 -213
  56. erispulse-2.10.1/src/ErisPulse/loaders/adapter.py +0 -356
  57. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/.gitignore +0 -0
  58. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/LICENSE +0 -0
  59. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/README.pypi.md +0 -0
  60. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/__init__.py +0 -0
  61. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/commands/__init__.py +0 -0
  62. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/console.py +0 -0
  63. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/constants.py +0 -0
  64. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/hints.py +0 -0
  65. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
  66. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
  67. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/registry.py +0 -0
  68. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/utils/__init__.py +0 -0
  69. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/utils/display.py +0 -0
  70. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/CLI/utils/interactive.py +0 -0
  71. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/__init__.py +0 -0
  72. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/api_dsl.py +0 -0
  73. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/client.py +0 -0
  74. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/converter.py +0 -0
  75. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/errors.py +0 -0
  76. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
  77. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/kv_builder.py +0 -0
  78. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/manager.py +0 -0
  79. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/model.py +0 -0
  80. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/router.py +0 -0
  81. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
  82. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
  83. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/sql_base.py +0 -0
  84. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/storage.py +0 -0
  85. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Bases/websocket.py +0 -0
  86. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/command_args.py +0 -0
  87. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/conversation.py +0 -0
  88. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/governance.py +0 -0
  89. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/interaction.py +0 -0
  90. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/message.py +0 -0
  91. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/message_builder.py +0 -0
  92. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/meta.py +0 -0
  93. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/notice.py +0 -0
  94. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/overrides.py +0 -0
  95. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/request.py +0 -0
  96. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/session_type.py +0 -0
  97. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/throttle.py +0 -0
  98. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/Event/trace.py +0 -0
  99. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/__init__.py +0 -0
  100. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/assets/__init__.py +0 -0
  101. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/assets/error.html +0 -0
  102. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/assets/root.html +0 -0
  103. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/client.py +0 -0
  104. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/connections.py +0 -0
  105. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/di.py +0 -0
  106. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/i18n/__init__.py +0 -0
  107. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/i18n/constants.py +0 -0
  108. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
  109. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/logger.py +0 -0
  110. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/master.py +0 -0
  111. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/ownership.py +0 -0
  112. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/scope.py +0 -0
  113. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/shadow.py +0 -0
  114. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/storage/__init__.py +0 -0
  115. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/storage/mysql.py +0 -0
  116. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/storage/postgres.py +0 -0
  117. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/storage/sqlite.py +0 -0
  118. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/text_match.py +0 -0
  119. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/Core/transcript.py +0 -0
  120. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/__init__.py +0 -0
  121. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/__main__.py +0 -0
  122. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/finders/__init__.py +0 -0
  123. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/finders/adapter.py +0 -0
  124. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/finders/bases/__init__.py +0 -0
  125. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/finders/bases/finder.py +0 -0
  126. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/finders/module.py +0 -0
  127. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/loaders/__init__.py +0 -0
  128. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/loaders/bases/__init__.py +0 -0
  129. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/loaders/bases/loader.py +0 -0
  130. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/loaders/strategy.py +0 -0
  131. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/loaders/strict.py +0 -0
  132. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/py.typed +0 -0
  133. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/__init__.py +0 -0
  134. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/config_schema.py +0 -0
  135. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/context.py +0 -0
  136. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/diagnostics.py +0 -0
  137. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/exceptions.py +0 -0
  138. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/file_watcher.py +0 -0
  139. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/frame_config.py +0 -0
  140. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/hints.py +0 -0
  141. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/memory.py +0 -0
  142. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/owner_cleanup.py +0 -0
  143. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/plugin_reload.py +0 -0
  144. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/proactive_gc.py +0 -0
  145. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/tasks.py +0 -0
  146. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/troubleshoot.py +0 -0
  147. {erispulse-2.10.1 → erispulse-2.11.0.dev0}/src/ErisPulse/runtime/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ErisPulse
3
- Version: 2.10.1
3
+ Version: 2.11.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
@@ -33,7 +33,7 @@ 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
36
+ Requires-Dist: tomlclass<0.4,>=0.3.2
37
37
  Requires-Dist: uvicorn<1.0,>=0.35.0
38
38
  Requires-Dist: websockets>=12.0
39
39
  Provides-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.1"
7
+ version = "2.11.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"
@@ -62,7 +62,8 @@ 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",
@@ -102,6 +103,7 @@ test = [
102
103
  "pytest-timeout>=2.1.0",
103
104
  "pytest-mock>=3.10.0",
104
105
  "httpx>=0.24.0",
106
+ "tomli>=2.0.0; python_version < '3.11'",
105
107
  ]
106
108
 
107
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
@@ -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 (
@@ -207,8 +235,13 @@ class Main(BaseModule):
207
235
  group=\"default\",
208
236
  tags=[\"{name}\"],
209
237
  # 对外服务白名单(可选):声明后其他模块可经 sdk.module.call() 调用这些方法;
210
- # 缺省时公开方法全开放(限制由用户通过 scope.actions 配置)
211
- # 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
+ # ],
212
245
  )
213
246
 
214
247
  @staticmethod
@@ -561,6 +594,7 @@ class {name}(BaseAdapter):
561
594
  return self.make_error(message=str(e))
562
595
  """
563
596
 
597
+
564
598
  _ADAPTER_CONVERTER = """\"\"\"
565
599
  {text[adapter.converter_doc]}
566
600
  \"\"\"
@@ -760,12 +794,13 @@ class CreateCommand(Command):
760
794
  name = "create"
761
795
  description = i18n.t("cli.create.description")
762
796
  aliases = ["c", "new"]
797
+ category = "project"
763
798
 
764
799
  def add_arguments(self, parser: ArgumentParser):
765
800
  parser.add_argument(
766
801
  "create_type",
767
802
  nargs="?",
768
- choices=["module", "adapter"],
803
+ choices=["module", "adapter", "pack"],
769
804
  help=i18n.t("cli.create.type_help"),
770
805
  )
771
806
  parser.add_argument("--name", "-n", help=i18n.t("cli.create.name_help"))
@@ -791,7 +826,7 @@ class CreateCommand(Command):
791
826
  "--local",
792
827
  action="store_true",
793
828
  default=False,
794
- help=i18n.t("cli.create.local_module_help"),
829
+ help=i18n.t("cli.create.local_help"),
795
830
  )
796
831
 
797
832
  def execute(self, args):
@@ -799,6 +834,11 @@ class CreateCommand(Command):
799
834
  if not create_type:
800
835
  create_type = self._interactive_select_type()
801
836
 
837
+ # pack:本地插件 → 可发布项目(无需组件名,直接进入转换流程)
838
+ if create_type == "pack":
839
+ self._create_pack(args)
840
+ return
841
+
802
842
  default_name = "MyModule" if create_type == "module" else "MyAdapter"
803
843
  name = args.name
804
844
  # 名称校验:非法时保留输入并重新提示,而非直接退出
@@ -828,12 +868,15 @@ class CreateCommand(Command):
828
868
  console.print(
829
869
  f" [bold]2.[/] Adapter [dim]{i18n.t('cli.create.select_type_adapter')}[/]"
830
870
  )
871
+ console.print(
872
+ f" [bold]3.[/] Pack [dim]{i18n.t('cli.create.select_type_pack')}[/]"
873
+ )
831
874
  console.print()
832
875
  choice = IntPrompt.ask(
833
- i18n.t("cli.create.select_prompt"), default=1, choices=["1", "2"]
876
+ i18n.t("cli.create.select_prompt"), default=1, choices=["1", "2", "3"]
834
877
  )
835
878
  console.print()
836
- return "module" if choice == 1 else "adapter"
879
+ return ("module", "adapter", "pack")[choice - 1]
837
880
 
838
881
  def _ask_missing(
839
882
  self, args, field_name: str, prompt_text: str, default: str = ""
@@ -852,6 +895,184 @@ class CreateCommand(Command):
852
895
  val = Prompt.ask(f" {prompt_text}", default=default)
853
896
  return val
854
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
+
855
1076
  def _create_module(self, args, name: str):
856
1077
  """
857
1078
  创建 Module 项目脚手架
@@ -999,6 +1220,80 @@ class CreateCommand(Command):
999
1220
  console.print(f"[error] {i18n.t('cli.create.failed', error=e)}")
1000
1221
  sys.exit(1)
1001
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
+
1002
1297
  def _create_adapter(self, args, name: str):
1003
1298
  """
1004
1299
  创建 Adapter 项目脚手架
@@ -1007,6 +1302,11 @@ class CreateCommand(Command):
1007
1302
  :param name: [str] 适配器名称
1008
1303
  :return: [None] 无返回值
1009
1304
  """
1305
+ # --local:创建本地适配器插件(plugins/ 目录结构,免打包安装)
1306
+ if getattr(args, "local", False):
1307
+ self._create_adapter_local(args, name)
1308
+ return
1309
+
1010
1310
  description = self._ask_missing(
1011
1311
  args,
1012
1312
  "description",