ErisPulse 2.8.6__tar.gz → 2.9.0.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/.gitignore +7 -2
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/PKG-INFO +5 -3
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/pyproject.toml +22 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/cli.py +12 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/create.py +2 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/doctor.py +47 -1
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/init.py +11 -7
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/install.py +10 -6
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/list.py +40 -6
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/run.py +29 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/self_update.py +5 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/upgrade.py +9 -3
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/constants.py +16 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/en.py +13 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/ja.py +13 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/ru.py +13 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +13 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +13 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/config_wizard.py +7 -6
- erispulse-2.9.0.dev2/src/ErisPulse/CLI/utils/interactive.py +127 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/package_manager.py +9 -6
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/__init__.py +8 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/adapter.py +62 -656
- erispulse-2.9.0.dev2/src/ErisPulse/Core/Bases/api_dsl.py +698 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/config_schema.py +215 -17
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/kv_builder.py +4 -2
- erispulse-2.9.0.dev2/src/ErisPulse/Core/Bases/model.py +1167 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/module.py +8 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/send_rules.py +8 -2
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/sql_base.py +175 -13
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/__init__.py +14 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/base.py +76 -7
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/command.py +609 -22
- erispulse-2.9.0.dev2/src/ErisPulse/Core/Event/command_args.py +539 -0
- erispulse-2.9.0.dev2/src/ErisPulse/Core/Event/conversation.py +773 -0
- erispulse-2.9.0.dev2/src/ErisPulse/Core/Event/governance.py +474 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/interaction.py +3 -2
- erispulse-2.9.0.dev2/src/ErisPulse/Core/Event/message.py +299 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/overrides.py +10 -8
- erispulse-2.9.0.dev2/src/ErisPulse/Core/Event/throttle.py +180 -0
- erispulse-2.9.0.dev2/src/ErisPulse/Core/Event/trace.py +134 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/wrapper.py +54 -748
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/__init__.py +20 -1
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/adapter.py +273 -94
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/client.py +24 -19
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/config.py +263 -25
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/constants.py +78 -7
- erispulse-2.9.0.dev2/src/ErisPulse/Core/di.py +226 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/en.py +89 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/ja.py +89 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/ru.py +89 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/zh_cn.py +89 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/zh_tw.py +89 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/lifecycle.py +137 -70
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/logger.py +4 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/master.py +1 -1
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/module.py +273 -190
- erispulse-2.9.0.dev2/src/ErisPulse/Core/ownership.py +517 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/router.py +391 -71
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/scope.py +326 -190
- erispulse-2.9.0.dev2/src/ErisPulse/Core/shadow.py +497 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/storage/mysql.py +23 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/storage/postgres.py +19 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/storage/sqlite.py +11 -2
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/text_match.py +1 -1
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/transcript.py +55 -4
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/module.py +386 -5
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/__init__.py +9 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/diagnostics.py +126 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/exceptions.py +9 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/file_watcher.py +29 -2
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/plugin_reload.py +3 -2
- erispulse-2.9.0.dev2/src/ErisPulse/runtime/proactive_gc.py +292 -0
- erispulse-2.9.0.dev2/src/ErisPulse/runtime/sdk_initializer.py +708 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/tasks.py +8 -2
- erispulse-2.9.0.dev2/src/ErisPulse/runtime/troubleshoot.py +233 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/sdk.py +62 -923
- erispulse-2.8.6/src/ErisPulse/Core/Event/message.py +0 -197
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/LICENSE +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/README.pypi.md +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/base.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/config.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/language.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/types.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/commands/version.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/console.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/hints.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/registry.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/display.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/CLI/utils/scaffold_text.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/client.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/converter.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/errors.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/manager.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/router.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/storage.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Bases/websocket.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/message_builder.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/meta.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/notice.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/request.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/Event/session_type.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/error.css +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/error.html +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/root.css +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/assets/root.html +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/constants.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/Core/storage/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/__main__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/adapter.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/bases/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/bases/finder.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/finders/module.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/adapter.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/bases/__init__.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/bases/loader.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/plugin_folder.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/strategy.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/loaders/strict.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/py.typed +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/config_schema.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/context.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/example_config.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/frame_config.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/hints.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/memory.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/owner_cleanup.py +0 -0
- {erispulse-2.8.6 → erispulse-2.9.0.dev2}/src/ErisPulse/runtime/version.py +0 -0
|
@@ -5,7 +5,6 @@ plugins/
|
|
|
5
5
|
venvs
|
|
6
6
|
config.full.example
|
|
7
7
|
config.toml
|
|
8
|
-
uv.lock
|
|
9
8
|
snapshots/
|
|
10
9
|
logs/
|
|
11
10
|
config.db*
|
|
@@ -123,7 +122,7 @@ ipython_config.py
|
|
|
123
122
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
124
123
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
125
124
|
# commonly ignored for libraries.
|
|
126
|
-
|
|
125
|
+
uv.lock
|
|
127
126
|
|
|
128
127
|
# poetry
|
|
129
128
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
@@ -239,3 +238,9 @@ pyvenv.cfg
|
|
|
239
238
|
|
|
240
239
|
# 但保留源中文文档
|
|
241
240
|
!docs/zh-CN/
|
|
241
|
+
|
|
242
|
+
.zcode/
|
|
243
|
+
|
|
244
|
+
.github/assets/ErisPulseFilm1.mp4
|
|
245
|
+
.github/assets/score1.wav
|
|
246
|
+
.github/assets/score2.wav
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ErisPulse
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.0-dev.2
|
|
4
4
|
Summary: Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署
|
|
5
5
|
Project-URL: Homepage, https://github.com/ErisPulse/ErisPulse
|
|
6
6
|
Project-URL: Documentation, https://www.erisdev.com#docs
|
|
@@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
26
|
Classifier: Programming Language :: Python :: 3.13
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
27
28
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
28
29
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
30
|
Classifier: Typing :: Typed
|
|
@@ -33,18 +34,19 @@ Requires-Dist: aiosqlite>=0.20.0
|
|
|
33
34
|
Requires-Dist: fastapi>=0.116.1
|
|
34
35
|
Requires-Dist: rich>=13.0.0
|
|
35
36
|
Requires-Dist: tomlkit>=0.15.1
|
|
36
|
-
Requires-Dist: uvicorn>=0.
|
|
37
|
+
Requires-Dist: uvicorn>=0.35.0
|
|
37
38
|
Requires-Dist: websockets>=12.0
|
|
38
39
|
Provides-Extra: mysql
|
|
39
40
|
Requires-Dist: aiomysql>=0.2.0; extra == 'mysql'
|
|
41
|
+
Requires-Dist: cryptography>=41.0.0; extra == 'mysql'
|
|
40
42
|
Provides-Extra: postgres
|
|
41
43
|
Requires-Dist: asyncpg>=0.29.0; extra == 'postgres'
|
|
42
44
|
Provides-Extra: test
|
|
43
45
|
Requires-Dist: httpx>=0.24.0; extra == 'test'
|
|
44
46
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
|
|
45
|
-
Requires-Dist: pytest-benchmark>=4.0.0; extra == 'test'
|
|
46
47
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
|
|
47
48
|
Requires-Dist: pytest-mock>=3.10.0; extra == 'test'
|
|
49
|
+
Requires-Dist: pytest-timeout>=2.1.0; extra == 'test'
|
|
48
50
|
Requires-Dist: pytest-xdist>=3.0.0; extra == 'test'
|
|
49
51
|
Requires-Dist: pytest>=7.0.0; extra == 'test'
|
|
50
52
|
Description-Content-Type: text/markdown
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ErisPulse"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.9.0-dev.2"
|
|
8
8
|
description = "Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署"
|
|
9
9
|
readme = "README.pypi.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -51,6 +51,7 @@ classifiers = [
|
|
|
51
51
|
"Programming Language :: Python :: 3.11",
|
|
52
52
|
"Programming Language :: Python :: 3.12",
|
|
53
53
|
"Programming Language :: Python :: 3.13",
|
|
54
|
+
"Programming Language :: Python :: 3.14",
|
|
54
55
|
"Programming Language :: Python :: 3 :: Only",
|
|
55
56
|
"Operating System :: OS Independent",
|
|
56
57
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -66,7 +67,7 @@ dependencies = [
|
|
|
66
67
|
"websockets>=12.0",
|
|
67
68
|
"aiohttp>=3.9.0",
|
|
68
69
|
"fastapi>=0.116.1",
|
|
69
|
-
"uvicorn>=0.
|
|
70
|
+
"uvicorn>=0.35.0",
|
|
70
71
|
# storage 相关(默认 SQLite 后端异步驱动,开箱即用)
|
|
71
72
|
"aiosqlite>=0.20.0",
|
|
72
73
|
]
|
|
@@ -83,6 +84,8 @@ Changelog = "https://github.com/ErisPulse/ErisPulse/blob/main/CHANGELOG.md"
|
|
|
83
84
|
# 存储后端可选驱动:pip install ErisPulse[mysql] / ErisPulse[postgres]
|
|
84
85
|
mysql = [
|
|
85
86
|
"aiomysql>=0.2.0",
|
|
87
|
+
# MySQL 8 默认 caching_sha2_password 认证要求(缺失时连接池初始化失败)
|
|
88
|
+
"cryptography>=41.0.0",
|
|
86
89
|
]
|
|
87
90
|
postgres = [
|
|
88
91
|
"asyncpg>=0.29.0",
|
|
@@ -92,12 +95,14 @@ test = [
|
|
|
92
95
|
"pytest-cov>=4.0.0",
|
|
93
96
|
"pytest-asyncio>=0.21.0",
|
|
94
97
|
"pytest-xdist>=3.0.0",
|
|
98
|
+
"pytest-timeout>=2.1.0",
|
|
95
99
|
"pytest-mock>=3.10.0",
|
|
96
|
-
"pytest-benchmark>=4.0.0",
|
|
97
100
|
"httpx>=0.24.0",
|
|
98
101
|
]
|
|
99
102
|
|
|
100
103
|
[dependency-groups]
|
|
104
|
+
# 测试依赖统一维护于 [project.optional-dependencies].test(公开 extras,
|
|
105
|
+
# 生态可 pip install ErisPulse[test]);此处的 test 组已删除以免双处漂移。
|
|
101
106
|
dev = [
|
|
102
107
|
"pyclean>=3.6.0",
|
|
103
108
|
"ruff>=0.1.7",
|
|
@@ -105,6 +110,9 @@ dev = [
|
|
|
105
110
|
"typing_extensions>=4.7.0; python_version < '3.11'",
|
|
106
111
|
]
|
|
107
112
|
|
|
113
|
+
[tool.uv]
|
|
114
|
+
default-groups = ["dev"]
|
|
115
|
+
|
|
108
116
|
[project.scripts]
|
|
109
117
|
ep-init = "ErisPulse.__init__:init_sync"
|
|
110
118
|
epsdk = "ErisPulse.__main__:main"
|
|
@@ -130,7 +138,7 @@ include = [
|
|
|
130
138
|
target-version = "py310"
|
|
131
139
|
line-length = 120
|
|
132
140
|
|
|
133
|
-
include = ["src/**/*.py", "tests/**/*.py"]
|
|
141
|
+
include = ["src/**/*.py", "tests/**/*.py", "scripts/**/*.py", "examples/**/*.py"]
|
|
134
142
|
|
|
135
143
|
extend-exclude = [
|
|
136
144
|
"__pycache__",
|
|
@@ -168,6 +176,7 @@ ignore = [
|
|
|
168
176
|
"RUF003", # ambiguous-unicode-character-comment
|
|
169
177
|
|
|
170
178
|
# 框架/异步代码风格性规则(强行修会破坏 API 或语义)
|
|
179
|
+
"B008", # function-call-in-default-argument —— 依赖注入(Depends)声明的标准形态,DI 框架惯例豁免
|
|
171
180
|
"PLC0415", # import-outside-top-level —— 异步框架中 lazy import 是常用且必要的模式
|
|
172
181
|
"PLR0911", # too-many-return-statements
|
|
173
182
|
"PLR0912", # too-many-branches
|
|
@@ -208,6 +217,13 @@ ignore = [
|
|
|
208
217
|
|
|
209
218
|
[tool.ruff.lint.per-file-ignores]
|
|
210
219
|
"__init__.py" = ["F401", "PLC0415", "I001"] # 导出式 __init__ 经常按需导入
|
|
220
|
+
# 旧版生成/翻译脚本:存量风格债渐进收编(新脚本 check_i18n_locales/check_docs_links/
|
|
221
|
+
# release_check/_common 已全净)。TODO: 逐步修复后移除本段。
|
|
222
|
+
"scripts/tools/translate-docs.py" = ["PERF401", "PTH", "UP035", "B", "RET505", "FURB", "RUF013", "UP008", "A001", "SIM"]
|
|
223
|
+
"scripts/tools/check-translation.py" = ["PERF401", "PTH", "UP035", "B", "RET505", "FURB", "RUF013", "SIM"]
|
|
224
|
+
"scripts/tools/generate-api-docs.py" = ["PERF401", "PTH", "UP035", "B", "RET505", "FURB", "RUF013", "A001", "SIM", "W29", "E501"]
|
|
225
|
+
"scripts/tools/generate-docs-index.py" = ["PERF401", "PTH", "UP035", "B", "RET505", "FURB", "RUF013", "RUF015", "SIM"]
|
|
226
|
+
"scripts/tools/generate-type-stubs.py" = ["PERF401", "PTH", "UP035", "B", "RET505", "FURB", "RUF013", "ANN", "A001", "PLW0603", "SIM", "W29"]
|
|
211
227
|
"tests/**/*.py" = [
|
|
212
228
|
# 断言/夹具相关(测试代码天然模式)
|
|
213
229
|
"S101", "PLR2004", "SLF001", "ARG", "ANN", "PTH",
|
|
@@ -282,6 +298,8 @@ python_classes = "Test*"
|
|
|
282
298
|
python_functions = "test_*"
|
|
283
299
|
minversion = "7.0"
|
|
284
300
|
addopts = [
|
|
301
|
+
"--timeout=120",
|
|
302
|
+
"--timeout-method=thread",
|
|
285
303
|
"-v",
|
|
286
304
|
"--strict-markers",
|
|
287
305
|
"--strict-config",
|
|
@@ -305,6 +305,11 @@ class CLI:
|
|
|
305
305
|
args, unknown = self.parser.parse_known_args()
|
|
306
306
|
args._unknown_args = unknown
|
|
307
307
|
|
|
308
|
+
# --yes/-y:assume-yes 模式,由统一交互封装(utils/interactive)消费
|
|
309
|
+
from .utils.interactive import set_assume_yes
|
|
310
|
+
|
|
311
|
+
set_assume_yes(bool(getattr(args, "yes", False)))
|
|
312
|
+
|
|
308
313
|
# --no-color:禁用 Rich 控制台着色(CI / 日志采集场景)
|
|
309
314
|
if getattr(args, "no_color", False):
|
|
310
315
|
console.no_color = True
|
|
@@ -375,6 +380,13 @@ class CLI:
|
|
|
375
380
|
console.print(f"\n[warning]{i18n.t('cli.run.user_interrupted')}[/]")
|
|
376
381
|
sys.exit(1)
|
|
377
382
|
except Exception as e:
|
|
383
|
+
from .utils.interactive import NonInteractiveError
|
|
384
|
+
|
|
385
|
+
if isinstance(e, NonInteractiveError):
|
|
386
|
+
# 非交互环境遇到无法自动应答的提示:明确报错而非 EOF 崩溃
|
|
387
|
+
console.print(f"[error]{e}[/]")
|
|
388
|
+
console.print(f"[hint]{i18n.t('cli.interactive.non_tty_hint')}[/]")
|
|
389
|
+
sys.exit(1)
|
|
378
390
|
console.print(f"[error]{i18n.t('cli.run.exec_error', error=e)}[/]")
|
|
379
391
|
# 场景化友好提示:根据异常类型给出下一步建议
|
|
380
392
|
from .hints import suggest_for_exception
|
|
@@ -219,6 +219,8 @@ class Main(BaseModule):
|
|
|
219
219
|
# 跨命令的用户黑白名单用覆写系统 ACL
|
|
220
220
|
# (ErisPulse.event.overrides.acl 或 overrides.acl.set(name, allow/deny),命令名支持 glob);
|
|
221
221
|
# 模块级可用性 / 事件准入 / 出站限制由作用域 scope 管理(用户可控)
|
|
222
|
+
# 命令支持声明式参数与选项(args= / options=):框架自动解析并按名注入处理器参数,
|
|
223
|
+
# 用户输入错误自动回复本地化提示与用法(类型:str/int/float/bool/literal/duration/rest)
|
|
222
224
|
@command(\"hello\", help=i18n.t(\"module.{name}.command.hello.help\"))
|
|
223
225
|
async def hello_command(event: Event):
|
|
224
226
|
await event.reply(i18n.t(\"module.{name}.command.hello.reply\"))
|
|
@@ -95,7 +95,7 @@ class DoctorCommand(Command):
|
|
|
95
95
|
pip_note = (
|
|
96
96
|
i18n.t("cli.doctor.venv_pip_ok")
|
|
97
97
|
if venv_pip.exists()
|
|
98
|
-
else "
|
|
98
|
+
else i18n.t("cli.doctor.venv_pip_uv")
|
|
99
99
|
)
|
|
100
100
|
rows.append(
|
|
101
101
|
(
|
|
@@ -133,6 +133,52 @@ class DoctorCommand(Command):
|
|
|
133
133
|
)
|
|
134
134
|
failed = True
|
|
135
135
|
|
|
136
|
+
# 4.5 组件配置健康(配置文件存在 ≠ 必填项齐全:复用配置向导的
|
|
137
|
+
# schema 状态检测,"装好了跑不起来"多因适配器必填项缺失)
|
|
138
|
+
try:
|
|
139
|
+
from ..utils.config_wizard import (
|
|
140
|
+
STATUS_INCOMPLETE,
|
|
141
|
+
get_target_status,
|
|
142
|
+
load_config_targets,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
incomplete: list[str] = []
|
|
146
|
+
for target in load_config_targets():
|
|
147
|
+
status, _errors = get_target_status(target)
|
|
148
|
+
# 仅"待完善"(开了但必填项缺失)判 FAIL;完全未配置属
|
|
149
|
+
# 用户尚未开始配置,不算故障
|
|
150
|
+
if status == STATUS_INCOMPLETE:
|
|
151
|
+
incomplete.append(target.name)
|
|
152
|
+
if incomplete:
|
|
153
|
+
failed = True
|
|
154
|
+
rows.append(
|
|
155
|
+
(
|
|
156
|
+
self._status(False),
|
|
157
|
+
i18n.t("cli.doctor.component_config"),
|
|
158
|
+
i18n.t(
|
|
159
|
+
"cli.doctor.component_config_incomplete",
|
|
160
|
+
names=", ".join(incomplete),
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
rows.append(
|
|
166
|
+
(
|
|
167
|
+
self._status(True),
|
|
168
|
+
i18n.t("cli.doctor.component_config"),
|
|
169
|
+
i18n.t("cli.doctor.component_config_ok"),
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
except Exception:
|
|
173
|
+
# 发现失败(如依赖缺失)不阻塞其余诊断项
|
|
174
|
+
rows.append(
|
|
175
|
+
(
|
|
176
|
+
self._status(True),
|
|
177
|
+
i18n.t("cli.doctor.component_config"),
|
|
178
|
+
i18n.t("cli.doctor.component_config_skipped"),
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
136
182
|
# 5. PyPI 连通性
|
|
137
183
|
try:
|
|
138
184
|
remote = asyncio.run(pm.get_remote_packages())
|
|
@@ -11,7 +11,6 @@ import sys
|
|
|
11
11
|
from argparse import ArgumentParser
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
from rich.prompt import Confirm, IntPrompt
|
|
15
14
|
from rich.text import Text
|
|
16
15
|
|
|
17
16
|
from ..base import Command
|
|
@@ -19,6 +18,7 @@ from ..console import console
|
|
|
19
18
|
from ..i18n import i18n
|
|
20
19
|
from ..utils import PackageManager
|
|
21
20
|
from ..utils.display import _input, prompt_validated, section_header
|
|
21
|
+
from ..utils.interactive import confirm, select
|
|
22
22
|
from ..utils.package_manager import (
|
|
23
23
|
append_pyproject_dependencies,
|
|
24
24
|
create_project_venv,
|
|
@@ -432,7 +432,11 @@ class InitCommand(Command):
|
|
|
432
432
|
f" [bold]2.[/] {i18n.t('cli.init.location_option_new')} [dim]— {i18n.t('cli.init.location_desc_new')}[/]"
|
|
433
433
|
)
|
|
434
434
|
console.print()
|
|
435
|
-
location_choice =
|
|
435
|
+
location_choice = int(
|
|
436
|
+
select(
|
|
437
|
+
i18n.t("cli.create.select_prompt"), choices=["1", "2"], default="2"
|
|
438
|
+
)
|
|
439
|
+
)
|
|
436
440
|
console.print()
|
|
437
441
|
in_current_dir = location_choice == 1
|
|
438
442
|
|
|
@@ -458,7 +462,7 @@ class InitCommand(Command):
|
|
|
458
462
|
project_name = p.name
|
|
459
463
|
project_path = (Path(target_dir) if target_dir else Path()) / project_name
|
|
460
464
|
if project_path.exists() and not force:
|
|
461
|
-
if not
|
|
465
|
+
if not confirm(
|
|
462
466
|
f" [cyan]{i18n.t('cli.init.dir_overwrite_prompt', name=project_name)}[/]",
|
|
463
467
|
default=False,
|
|
464
468
|
):
|
|
@@ -467,10 +471,10 @@ class InitCommand(Command):
|
|
|
467
471
|
|
|
468
472
|
# 虚拟环境与 git 仓库问询
|
|
469
473
|
if not getattr(self, "no_venv", False):
|
|
470
|
-
create_venv =
|
|
474
|
+
create_venv = confirm(
|
|
471
475
|
f" [cyan]{i18n.t('cli.init.venv_prompt')}[/]", default=True
|
|
472
476
|
)
|
|
473
|
-
git_init =
|
|
477
|
+
git_init = confirm(
|
|
474
478
|
f" [cyan]{i18n.t('cli.init.git_prompt')}[/]", default=False
|
|
475
479
|
)
|
|
476
480
|
|
|
@@ -521,7 +525,7 @@ class InitCommand(Command):
|
|
|
521
525
|
console.print(f"[warning] {i18n.t('cli.init.invalid_port', port=new_port)}[/]")
|
|
522
526
|
new_port = _input(">")
|
|
523
527
|
|
|
524
|
-
if
|
|
528
|
+
if confirm(
|
|
525
529
|
f"\n [cyan]{i18n.t('cli.init.configure_adapters_prompt')}[/]",
|
|
526
530
|
default=True,
|
|
527
531
|
):
|
|
@@ -587,7 +591,7 @@ class InitCommand(Command):
|
|
|
587
591
|
|
|
588
592
|
console.print(f"[dim] {i18n.t('cli.init.adapters_enabled', count=len(enabled))}[/]")
|
|
589
593
|
|
|
590
|
-
if enabled and
|
|
594
|
+
if enabled and confirm(f" [cyan]{i18n.t('cli.init.install_selected_prompt')}[/]", default=True):
|
|
591
595
|
self._install_adapters(enabled, adapters)
|
|
592
596
|
|
|
593
597
|
def _install_adapters(self, adapter_names, adapters_info):
|
|
@@ -8,7 +8,7 @@ import asyncio
|
|
|
8
8
|
import sys
|
|
9
9
|
from argparse import ArgumentParser
|
|
10
10
|
|
|
11
|
-
from rich.prompt import
|
|
11
|
+
from rich.prompt import Prompt
|
|
12
12
|
from rich.text import Text
|
|
13
13
|
|
|
14
14
|
from ..base import Command
|
|
@@ -16,6 +16,7 @@ from ..console import console
|
|
|
16
16
|
from ..i18n import i18n
|
|
17
17
|
from ..utils import PackageManager, config_wizard
|
|
18
18
|
from ..utils.display import interactive_select_table
|
|
19
|
+
from ..utils.interactive import confirm
|
|
19
20
|
from ..utils.package_manager import (
|
|
20
21
|
resolve_target_python,
|
|
21
22
|
warn_if_uv_isolated,
|
|
@@ -251,6 +252,7 @@ class InstallCommand(Command):
|
|
|
251
252
|
if success:
|
|
252
253
|
# 非交互路径不进入向导,仅提示后续配置入口
|
|
253
254
|
console.print(f"[dim] {i18n.t('cli.install.configure_hint')}[/]")
|
|
255
|
+
console.print(f"[hint] {i18n.t('cli.install.done_hint')}[/]")
|
|
254
256
|
|
|
255
257
|
if not success:
|
|
256
258
|
sys.exit(1)
|
|
@@ -294,7 +296,7 @@ class InstallCommand(Command):
|
|
|
294
296
|
elif choice == "4":
|
|
295
297
|
self._install_custom(upgrade, pre)
|
|
296
298
|
|
|
297
|
-
if not
|
|
299
|
+
if not confirm(f"\n [cyan]{i18n.t('cli.install.continue_install')}[/]", default=False):
|
|
298
300
|
break
|
|
299
301
|
|
|
300
302
|
def _install_adapters(self, remote_packages: dict, upgrade: bool, pre: bool):
|
|
@@ -341,7 +343,7 @@ class InstallCommand(Command):
|
|
|
341
343
|
|
|
342
344
|
selected_names = [name for name, _ in selected]
|
|
343
345
|
console.print(f"\n [dim]{i18n.t('cli.install.selected', selected=', '.join(selected_names))}[/]")
|
|
344
|
-
if
|
|
346
|
+
if confirm(
|
|
345
347
|
f" [cyan]{i18n.t('cli.install.confirm_adapters', count=len(selected_names))}[/]",
|
|
346
348
|
default=True,
|
|
347
349
|
):
|
|
@@ -390,7 +392,7 @@ class InstallCommand(Command):
|
|
|
390
392
|
|
|
391
393
|
selected_names = [name for name, _ in selected]
|
|
392
394
|
console.print(f"\n [dim]{i18n.t('cli.install.selected', selected=', '.join(selected_names))}[/]")
|
|
393
|
-
if
|
|
395
|
+
if confirm(
|
|
394
396
|
f" [cyan]{i18n.t('cli.install.confirm_modules', count=len(selected_names))}[/]",
|
|
395
397
|
default=True,
|
|
396
398
|
):
|
|
@@ -495,7 +497,7 @@ class InstallCommand(Command):
|
|
|
495
497
|
except ValueError:
|
|
496
498
|
continue
|
|
497
499
|
|
|
498
|
-
if selected and
|
|
500
|
+
if selected and confirm(
|
|
499
501
|
f" [cyan]{i18n.t('cli.install.confirm_search_install', count=len(selected))}[/]",
|
|
500
502
|
default=True,
|
|
501
503
|
):
|
|
@@ -514,7 +516,7 @@ class InstallCommand(Command):
|
|
|
514
516
|
if package_name.lower() == "q":
|
|
515
517
|
return
|
|
516
518
|
if package_name:
|
|
517
|
-
if
|
|
519
|
+
if confirm(
|
|
518
520
|
f" [cyan]{i18n.t('cli.install.confirm_custom_install', package_name=package_name)}[/]",
|
|
519
521
|
default=True,
|
|
520
522
|
):
|
|
@@ -534,6 +536,7 @@ class InstallCommand(Command):
|
|
|
534
536
|
"""
|
|
535
537
|
if not config_wizard.is_interactive():
|
|
536
538
|
console.print(f"[dim] {i18n.t('cli.install.configure_hint')}[/]")
|
|
539
|
+
console.print(f"[hint] {i18n.t('cli.install.done_hint')}[/]")
|
|
537
540
|
return
|
|
538
541
|
|
|
539
542
|
resolved = []
|
|
@@ -545,3 +548,4 @@ class InstallCommand(Command):
|
|
|
545
548
|
config_wizard.post_install_configure(resolved)
|
|
546
549
|
except Exception as e:
|
|
547
550
|
console.print(f"[warning] {i18n.t('cli.config.post_install_failed', error=e)}[/]")
|
|
551
|
+
console.print(f"[hint] {i18n.t('cli.install.done_hint')}[/]")
|
|
@@ -56,6 +56,7 @@ class ListCommand(Command):
|
|
|
56
56
|
|
|
57
57
|
# 仅在需要时一次性拉取远程索引,避免逐包重复 asyncio.run / 网络请求
|
|
58
58
|
remote_packages = None
|
|
59
|
+
remote_unavailable = False
|
|
59
60
|
if outdated_only:
|
|
60
61
|
try:
|
|
61
62
|
remote_packages = asyncio.run(
|
|
@@ -63,15 +64,29 @@ class ListCommand(Command):
|
|
|
63
64
|
)
|
|
64
65
|
except Exception:
|
|
65
66
|
remote_packages = None
|
|
67
|
+
# 索引拉取失败(异常或双源均空):区分"全是最新"与"无法检查"
|
|
68
|
+
remote_unavailable = remote_packages is None or not (
|
|
69
|
+
remote_packages.get("modules") or remote_packages.get("adapters")
|
|
70
|
+
)
|
|
66
71
|
|
|
67
72
|
if pkg_type == "all":
|
|
68
|
-
self._print_installed_packages(
|
|
69
|
-
|
|
73
|
+
self._print_installed_packages(
|
|
74
|
+
"modules", outdated_only, remote_packages, remote_unavailable
|
|
75
|
+
)
|
|
76
|
+
self._print_installed_packages(
|
|
77
|
+
"adapters", outdated_only, remote_packages, remote_unavailable
|
|
78
|
+
)
|
|
70
79
|
else:
|
|
71
|
-
self._print_installed_packages(
|
|
80
|
+
self._print_installed_packages(
|
|
81
|
+
pkg_type, outdated_only, remote_packages, remote_unavailable
|
|
82
|
+
)
|
|
72
83
|
|
|
73
84
|
def _print_installed_packages(
|
|
74
|
-
self,
|
|
85
|
+
self,
|
|
86
|
+
pkg_type: str,
|
|
87
|
+
outdated_only: bool = False,
|
|
88
|
+
remote_packages: dict | None = None,
|
|
89
|
+
remote_unavailable: bool = False,
|
|
75
90
|
):
|
|
76
91
|
"""
|
|
77
92
|
以表格形式打印已安装的模块或适配器
|
|
@@ -79,6 +94,7 @@ class ListCommand(Command):
|
|
|
79
94
|
:param pkg_type: [str] 组件类型 (modules 或 adapters)
|
|
80
95
|
:param outdated_only: [bool] 是否仅显示可升级的包 (默认: False)
|
|
81
96
|
:param remote_packages: [Optional[dict]] 预取的远程索引,避免逐包重复拉取 (默认: None)
|
|
97
|
+
:param remote_unavailable: [bool] 远程索引是否不可达(空结果区分文案用,默认: False)
|
|
82
98
|
"""
|
|
83
99
|
installed = self.package_manager.get_installed_packages()
|
|
84
100
|
|
|
@@ -121,6 +137,10 @@ class ListCommand(Command):
|
|
|
121
137
|
)
|
|
122
138
|
# 展示模块注册的脚本入口
|
|
123
139
|
self._print_package_scripts(installed["modules"])
|
|
140
|
+
elif outdated_only and remote_unavailable:
|
|
141
|
+
console.print(f"[dim] {i18n.t('cli.list.outdated_unavailable')}[/]")
|
|
142
|
+
elif outdated_only:
|
|
143
|
+
console.print(f"[dim] {i18n.t('cli.list.no_outdated')}[/]")
|
|
124
144
|
else:
|
|
125
145
|
console.print(f"[dim] {i18n.t('cli.list.no_modules')}[/]")
|
|
126
146
|
console.print(f"[dim] {i18n.t('cli.list.empty_hint')}[/]")
|
|
@@ -134,6 +154,7 @@ class ListCommand(Command):
|
|
|
134
154
|
)
|
|
135
155
|
table.add_column(i18n.t("cli.list.header_package"), min_width=20)
|
|
136
156
|
table.add_column(i18n.t("cli.list.header_version"), width=10)
|
|
157
|
+
table.add_column(i18n.t("cli.list.header_status"), width=8)
|
|
137
158
|
table.add_column(i18n.t("cli.list.header_desc"))
|
|
138
159
|
|
|
139
160
|
count = 0
|
|
@@ -142,10 +163,16 @@ class ListCommand(Command):
|
|
|
142
163
|
info["package"], info["version"], remote_packages
|
|
143
164
|
):
|
|
144
165
|
continue
|
|
166
|
+
status = (
|
|
167
|
+
f"[green]{i18n.t('cli.list.status_enabled')}[/]"
|
|
168
|
+
if info.get("enabled", True)
|
|
169
|
+
else f"[yellow]{i18n.t('cli.list.status_disabled')}[/]"
|
|
170
|
+
)
|
|
145
171
|
table.add_row(
|
|
146
172
|
name,
|
|
147
173
|
info["package"],
|
|
148
174
|
info["version"],
|
|
175
|
+
status,
|
|
149
176
|
info["summary"],
|
|
150
177
|
)
|
|
151
178
|
count += 1
|
|
@@ -155,6 +182,10 @@ class ListCommand(Command):
|
|
|
155
182
|
console.print(
|
|
156
183
|
f"[dim] {i18n.t('cli.list.count_adapters', count=count)}[/]"
|
|
157
184
|
)
|
|
185
|
+
elif outdated_only and remote_unavailable:
|
|
186
|
+
console.print(f"[dim] {i18n.t('cli.list.outdated_unavailable')}[/]")
|
|
187
|
+
elif outdated_only:
|
|
188
|
+
console.print(f"[dim] {i18n.t('cli.list.no_outdated')}[/]")
|
|
158
189
|
else:
|
|
159
190
|
console.print(f"[dim] {i18n.t('cli.list.no_adapters')}[/]")
|
|
160
191
|
console.print(f"[dim] {i18n.t('cli.list.empty_hint')}[/]")
|
|
@@ -182,12 +213,15 @@ class ListCommand(Command):
|
|
|
182
213
|
"""
|
|
183
214
|
if remote_packages is None:
|
|
184
215
|
remote_packages = asyncio.run(self.package_manager.get_remote_packages())
|
|
216
|
+
# 仅"远端更新"才算可升级:本地 dev/git 安装版本比远端新时不再误报
|
|
217
|
+
from ...runtime.version import compare_versions
|
|
218
|
+
|
|
185
219
|
for module_info in remote_packages["modules"].values():
|
|
186
220
|
if module_info["package"] == package_name:
|
|
187
|
-
return module_info["version"]
|
|
221
|
+
return compare_versions(module_info["version"], current_version) > 0
|
|
188
222
|
for adapter_info in remote_packages["adapters"].values():
|
|
189
223
|
if adapter_info["package"] == package_name:
|
|
190
|
-
return adapter_info["version"]
|
|
224
|
+
return compare_versions(adapter_info["version"], current_version) > 0
|
|
191
225
|
return False
|
|
192
226
|
|
|
193
227
|
def _print_package_scripts(self, packages: dict) -> None:
|
|
@@ -19,7 +19,14 @@ from rich.panel import Panel
|
|
|
19
19
|
from ...runtime.file_watcher import FileSystemEventHandler, PollingObserver
|
|
20
20
|
from ..base import Command
|
|
21
21
|
from ..console import console
|
|
22
|
-
from ..constants import
|
|
22
|
+
from ..constants import (
|
|
23
|
+
ENV_SUPERVISED,
|
|
24
|
+
HARD_RESTART_EXIT_CODE,
|
|
25
|
+
RUN_CRASH_BACKOFF_MAX_SECS,
|
|
26
|
+
RUN_CRASH_BACKOFF_PER_CRASH_SECS,
|
|
27
|
+
RUN_PERSISTENT_CRASH_HINT_THRESHOLD,
|
|
28
|
+
RUN_RESTART_PAUSE_SECS,
|
|
29
|
+
)
|
|
23
30
|
from ..i18n import i18n
|
|
24
31
|
from ..utils.package_manager import (
|
|
25
32
|
resolve_target_python,
|
|
@@ -152,7 +159,6 @@ class RunCommand(Command):
|
|
|
152
159
|
self._run_internal(reload_mode)
|
|
153
160
|
|
|
154
161
|
_RESTART_EXIT_CODE = HARD_RESTART_EXIT_CODE
|
|
155
|
-
_MAX_CRASH_BACKOFF = 60.0
|
|
156
162
|
|
|
157
163
|
def _run_internal(self, reload_mode: bool):
|
|
158
164
|
"""
|
|
@@ -200,6 +206,11 @@ class RunCommand(Command):
|
|
|
200
206
|
]
|
|
201
207
|
|
|
202
208
|
crash_count = 0
|
|
209
|
+
# 连续同退出码崩溃计数:达到阈值输出一次排查指引(确定性故障
|
|
210
|
+
# 退避重试无意义,与其反复失败不如引导留现场)
|
|
211
|
+
consecutive_same_code = 0
|
|
212
|
+
last_crash_code: int | None = None
|
|
213
|
+
crash_hint_shown = False
|
|
203
214
|
process = None
|
|
204
215
|
try:
|
|
205
216
|
while True:
|
|
@@ -213,7 +224,7 @@ class RunCommand(Command):
|
|
|
213
224
|
if process.returncode == self._RESTART_EXIT_CODE:
|
|
214
225
|
console.print(f"[info]{i18n.t('cli.run.restart_request')}[/]")
|
|
215
226
|
crash_count = 0
|
|
216
|
-
time.sleep(
|
|
227
|
+
time.sleep(RUN_RESTART_PAUSE_SECS)
|
|
217
228
|
continue
|
|
218
229
|
|
|
219
230
|
if process.returncode == 0:
|
|
@@ -224,7 +235,21 @@ class RunCommand(Command):
|
|
|
224
235
|
# 非硬重启/非正常退出码:模块/适配器内部错误导致子进程异常终止
|
|
225
236
|
# 不退出主进程,等待后自动重试
|
|
226
237
|
crash_count += 1
|
|
227
|
-
|
|
238
|
+
if process.returncode == last_crash_code:
|
|
239
|
+
consecutive_same_code += 1
|
|
240
|
+
else:
|
|
241
|
+
consecutive_same_code = 1
|
|
242
|
+
last_crash_code = process.returncode
|
|
243
|
+
if (
|
|
244
|
+
not crash_hint_shown
|
|
245
|
+
and RUN_PERSISTENT_CRASH_HINT_THRESHOLD > 0
|
|
246
|
+
and consecutive_same_code >= RUN_PERSISTENT_CRASH_HINT_THRESHOLD
|
|
247
|
+
):
|
|
248
|
+
crash_hint_shown = True
|
|
249
|
+
console.print(
|
|
250
|
+
f"[hint]{i18n.t('cli.run.persistent_crash_hint', count=consecutive_same_code, code=process.returncode)}[/]"
|
|
251
|
+
)
|
|
252
|
+
backoff = min(RUN_CRASH_BACKOFF_MAX_SECS, RUN_CRASH_BACKOFF_PER_CRASH_SECS * crash_count)
|
|
228
253
|
console.print(
|
|
229
254
|
f"[warning]{i18n.t('cli.run.process_crashed', code=process.returncode)}[/]"
|
|
230
255
|
)
|
|
@@ -9,7 +9,7 @@ import sys
|
|
|
9
9
|
from argparse import ArgumentParser
|
|
10
10
|
|
|
11
11
|
from rich.box import SIMPLE
|
|
12
|
-
from rich.prompt import
|
|
12
|
+
from rich.prompt import Prompt
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
from rich.text import Text
|
|
15
15
|
|
|
@@ -18,6 +18,7 @@ from ..console import console
|
|
|
18
18
|
from ..i18n import i18n
|
|
19
19
|
from ..utils import PackageManager
|
|
20
20
|
from ..utils.display import _input, section_header
|
|
21
|
+
from ..utils.interactive import confirm
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class SelfUpdateCommand(Command):
|
|
@@ -82,7 +83,7 @@ class SelfUpdateCommand(Command):
|
|
|
82
83
|
)
|
|
83
84
|
sys.exit(0)
|
|
84
85
|
elif not args.force:
|
|
85
|
-
if not
|
|
86
|
+
if not confirm(
|
|
86
87
|
i18n.t(
|
|
87
88
|
"cli.self_update.confirm_update",
|
|
88
89
|
current=current_version,
|
|
@@ -114,7 +115,7 @@ class SelfUpdateCommand(Command):
|
|
|
114
115
|
console.print(
|
|
115
116
|
f"[warning]{i18n.t('cli.self_update.version_not_found', version=specified_version)}[/]"
|
|
116
117
|
)
|
|
117
|
-
if not
|
|
118
|
+
if not confirm(
|
|
118
119
|
i18n.t("cli.self_update.confirm_continue"), default=False
|
|
119
120
|
):
|
|
120
121
|
return None
|
|
@@ -197,7 +198,7 @@ class SelfUpdateCommand(Command):
|
|
|
197
198
|
console.print(
|
|
198
199
|
f"[warning]{i18n.t('cli.self_update.version_not_found', version=target_version)}[/]"
|
|
199
200
|
)
|
|
200
|
-
if not
|
|
201
|
+
if not confirm(
|
|
201
202
|
i18n.t("cli.self_update.confirm_continue"), default=False
|
|
202
203
|
):
|
|
203
204
|
return None
|
|
@@ -7,11 +7,11 @@ Upgrade 命令实现
|
|
|
7
7
|
import sys
|
|
8
8
|
from argparse import ArgumentParser
|
|
9
9
|
|
|
10
|
-
from rich.prompt import Confirm
|
|
11
|
-
|
|
12
10
|
from ..base import Command
|
|
11
|
+
from ..console import console
|
|
13
12
|
from ..i18n import i18n
|
|
14
13
|
from ..utils import PackageManager
|
|
14
|
+
from ..utils.interactive import confirm
|
|
15
15
|
from ..utils.package_manager import resolve_target_python, warn_if_uv_isolated
|
|
16
16
|
|
|
17
17
|
|
|
@@ -50,15 +50,21 @@ class UpgradeCommand(Command):
|
|
|
50
50
|
def execute(self, args):
|
|
51
51
|
warn_if_uv_isolated()
|
|
52
52
|
self.package_manager.no_uv = getattr(args, "no_uv", False)
|
|
53
|
+
upgraded = False
|
|
53
54
|
if args.package:
|
|
54
55
|
# 升级指定包
|
|
55
56
|
success = self.package_manager.upgrade_package(args.package, pre=args.pre)
|
|
56
57
|
if not success:
|
|
57
58
|
sys.exit(1)
|
|
59
|
+
upgraded = True
|
|
58
60
|
# 升级所有包
|
|
59
|
-
elif args.force or
|
|
61
|
+
elif args.force or confirm(
|
|
60
62
|
i18n.t("cli.upgrade.confirm_all"), default=False
|
|
61
63
|
):
|
|
62
64
|
success = self.package_manager.upgrade_all()
|
|
63
65
|
if not success:
|
|
64
66
|
sys.exit(1)
|
|
67
|
+
upgraded = True
|
|
68
|
+
if upgraded:
|
|
69
|
+
# 生效方式提示:升级只更新磁盘代码,运行中的进程需重启或热重载
|
|
70
|
+
console.print(f"[hint]{i18n.t('cli.upgrade.done_hint')}[/]")
|