ErisPulse 2.1.12__py3-none-any.whl → 2.1.13rc1__py3-none-any.whl
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/__main__.py +778 -568
- {erispulse-2.1.12.dist-info → erispulse-2.1.13rc1.dist-info}/METADATA +1 -1
- {erispulse-2.1.12.dist-info → erispulse-2.1.13rc1.dist-info}/RECORD +6 -6
- {erispulse-2.1.12.dist-info → erispulse-2.1.13rc1.dist-info}/WHEEL +0 -0
- {erispulse-2.1.12.dist-info → erispulse-2.1.13rc1.dist-info}/entry_points.txt +0 -0
- {erispulse-2.1.12.dist-info → erispulse-2.1.13rc1.dist-info}/licenses/LICENSE +0 -0
ErisPulse/__main__.py
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ErisPulse SDK 命令行工具
|
|
4
|
+
|
|
5
|
+
一个功能强大的模块化系统管理工具,用于管理ErisPulse生态系统中的模块、适配器和扩展。
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import argparse
|
|
2
9
|
import importlib.metadata
|
|
3
10
|
import subprocess
|
|
@@ -7,7 +14,7 @@ import time
|
|
|
7
14
|
import json
|
|
8
15
|
import asyncio
|
|
9
16
|
from urllib.parse import urlparse
|
|
10
|
-
from typing import List, Dict, Tuple, Optional
|
|
17
|
+
from typing import List, Dict, Tuple, Optional, Callable, Any
|
|
11
18
|
from importlib.metadata import version, PackageNotFoundError
|
|
12
19
|
from watchdog.observers import Observer
|
|
13
20
|
from watchdog.events import FileSystemEventHandler
|
|
@@ -24,114 +31,127 @@ from rich.style import Style
|
|
|
24
31
|
from rich.theme import Theme
|
|
25
32
|
from rich.layout import Layout
|
|
26
33
|
from rich.live import Live
|
|
34
|
+
from rich.markdown import Markdown
|
|
35
|
+
from rich.highlighter import RegexHighlighter
|
|
36
|
+
from rich.logging import RichHandler
|
|
27
37
|
|
|
28
38
|
# 确保在Windows上启用颜色
|
|
29
|
-
import sys
|
|
30
39
|
if sys.platform == "win32":
|
|
31
40
|
from colorama import init
|
|
32
41
|
init()
|
|
33
42
|
|
|
43
|
+
# 自定义高亮器
|
|
44
|
+
class CommandHighlighter(RegexHighlighter):
|
|
45
|
+
"""高亮CLI命令和参数"""
|
|
46
|
+
highlights = [
|
|
47
|
+
r"(?P<switch>\-\-?\w+)",
|
|
48
|
+
r"(?P<option>\[\w+\])",
|
|
49
|
+
r"(?P<command>\b\w+\b)",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# 主题配置
|
|
34
53
|
theme = Theme({
|
|
35
|
-
"info": "cyan",
|
|
36
|
-
"success": "green",
|
|
37
|
-
"warning": "yellow",
|
|
38
|
-
"error": "red",
|
|
39
|
-
"title": "magenta",
|
|
54
|
+
"info": "dim cyan",
|
|
55
|
+
"success": "bold green",
|
|
56
|
+
"warning": "bold yellow",
|
|
57
|
+
"error": "bold red",
|
|
58
|
+
"title": "bold magenta",
|
|
40
59
|
"default": "default",
|
|
41
60
|
"progress": "green",
|
|
42
61
|
"progress.remaining": "white",
|
|
62
|
+
"cmd": "bold blue",
|
|
63
|
+
"param": "italic cyan",
|
|
64
|
+
"switch": "bold yellow",
|
|
65
|
+
"module": "bold green",
|
|
66
|
+
"adapter": "bold yellow",
|
|
67
|
+
"cli": "bold magenta",
|
|
43
68
|
})
|
|
44
69
|
|
|
45
|
-
|
|
70
|
+
# 全局控制台实例
|
|
71
|
+
console = Console(
|
|
72
|
+
theme=theme,
|
|
73
|
+
color_system="auto",
|
|
74
|
+
force_terminal=True,
|
|
75
|
+
highlighter=CommandHighlighter()
|
|
76
|
+
)
|
|
46
77
|
|
|
47
|
-
class
|
|
78
|
+
class PackageManager:
|
|
48
79
|
"""
|
|
49
|
-
|
|
80
|
+
ErisPulse包管理器
|
|
50
81
|
|
|
51
|
-
|
|
82
|
+
负责与包仓库交互,包括搜索、安装、卸载和升级ErisPulse组件
|
|
52
83
|
"""
|
|
53
84
|
REMOTE_SOURCES = [
|
|
54
85
|
"https://erisdev.com/packages.json",
|
|
55
86
|
"https://raw.githubusercontent.com/ErisPulse/ErisPulse-ModuleRepo/main/packages.json"
|
|
56
87
|
]
|
|
57
88
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
CACHE_EXPIRY = 3600 # 1小时缓存
|
|
90
|
+
|
|
91
|
+
def __init__(self):
|
|
92
|
+
self._cache = {}
|
|
93
|
+
self._cache_time = {}
|
|
62
94
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
:return:
|
|
66
|
-
Dict[str, Dict]: 包含模块和适配器的字典
|
|
67
|
-
- modules: 模块字典 {模块名: 模块信息}
|
|
68
|
-
- adapters: 适配器字典 {适配器名: 适配器信息}
|
|
69
|
-
|
|
70
|
-
:raises ClientError: 当网络请求失败时抛出
|
|
71
|
-
:raises asyncio.TimeoutError: 当请求超时时抛出
|
|
72
|
-
"""
|
|
95
|
+
async def _fetch_remote_packages(self, url: str) -> Optional[dict]:
|
|
96
|
+
"""从指定URL获取远程包数据"""
|
|
73
97
|
import aiohttp
|
|
74
98
|
from aiohttp import ClientError, ClientTimeout
|
|
75
99
|
|
|
76
|
-
timeout = ClientTimeout(total=
|
|
77
|
-
last_error = None
|
|
78
|
-
|
|
100
|
+
timeout = ClientTimeout(total=10)
|
|
79
101
|
try:
|
|
80
102
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
81
|
-
async with session.get(
|
|
103
|
+
async with session.get(url) as response:
|
|
82
104
|
if response.status == 200:
|
|
83
105
|
data = await response.text()
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
f"官方源请求失败,尝试备用源: {e}",
|
|
93
|
-
title="警告",
|
|
94
|
-
style="warning"
|
|
95
|
-
))
|
|
106
|
+
return json.loads(data)
|
|
107
|
+
except (ClientError, asyncio.TimeoutError, json.JSONDecodeError) as e:
|
|
108
|
+
console.print(f"[warning]获取远程包数据失败 ({url}): {e}[/]")
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
async def get_remote_packages(self, force_refresh: bool = False) -> dict:
|
|
112
|
+
"""
|
|
113
|
+
获取远程包列表,带缓存机制
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
:param force_refresh: 是否强制刷新缓存
|
|
116
|
+
:return: 包含模块和适配器的字典
|
|
117
|
+
"""
|
|
118
|
+
# 检查缓存
|
|
119
|
+
cache_key = "remote_packages"
|
|
120
|
+
if not force_refresh and cache_key in self._cache:
|
|
121
|
+
if time.time() - self._cache_time[cache_key] < self.CACHE_EXPIRY:
|
|
122
|
+
return self._cache[cache_key]
|
|
123
|
+
|
|
124
|
+
last_error = None
|
|
125
|
+
result = {"modules": {}, "adapters": {}, "cli_extensions": {}}
|
|
126
|
+
|
|
127
|
+
for url in self.REMOTE_SOURCES:
|
|
128
|
+
data = await self._fetch_remote_packages(url)
|
|
129
|
+
if data:
|
|
130
|
+
result["modules"].update(data.get("modules", {}))
|
|
131
|
+
result["adapters"].update(data.get("adapters", {}))
|
|
132
|
+
result["cli_extensions"].update(data.get("cli_extensions", {}))
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
# 更新缓存
|
|
136
|
+
self._cache[cache_key] = result
|
|
137
|
+
self._cache_time[cache_key] = time.time()
|
|
138
|
+
|
|
139
|
+
return result
|
|
117
140
|
|
|
118
|
-
|
|
119
|
-
def get_installed_packages() -> Dict[str, Dict[str, str]]:
|
|
141
|
+
def get_installed_packages(self) -> Dict[str, Dict[str, Dict[str, str]]]:
|
|
120
142
|
"""
|
|
121
143
|
获取已安装的包信息
|
|
122
144
|
|
|
123
|
-
:return:
|
|
124
|
-
Dict[str, Dict[str, Dict[str, str]]]: 已安装包字典
|
|
125
|
-
- modules: 已安装模块 {模块名: 模块信息}
|
|
126
|
-
- adapters: 已安装适配器 {适配器名: 适配器信息}
|
|
145
|
+
:return: 已安装包字典,包含模块、适配器和CLI扩展
|
|
127
146
|
"""
|
|
128
147
|
packages = {
|
|
129
148
|
"modules": {},
|
|
130
|
-
"adapters": {}
|
|
149
|
+
"adapters": {},
|
|
150
|
+
"cli_extensions": {}
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
try:
|
|
134
|
-
#
|
|
154
|
+
# 查找模块和适配器
|
|
135
155
|
for dist in importlib.metadata.distributions():
|
|
136
156
|
if "ErisPulse-" in dist.metadata["Name"]:
|
|
137
157
|
entry_points = dist.entry_points
|
|
@@ -140,7 +160,8 @@ class PyPIManager:
|
|
|
140
160
|
packages["modules"][ep.name] = {
|
|
141
161
|
"package": dist.metadata["Name"],
|
|
142
162
|
"version": dist.version,
|
|
143
|
-
"summary": dist.metadata["Summary"]
|
|
163
|
+
"summary": dist.metadata["Summary"],
|
|
164
|
+
"enabled": self._is_module_enabled(ep.name)
|
|
144
165
|
}
|
|
145
166
|
elif ep.group == "erispulse.adapter":
|
|
146
167
|
packages["adapters"][ep.name] = {
|
|
@@ -148,576 +169,765 @@ class PyPIManager:
|
|
|
148
169
|
"version": dist.version,
|
|
149
170
|
"summary": dist.metadata["Summary"]
|
|
150
171
|
}
|
|
172
|
+
|
|
173
|
+
# 查找CLI扩展
|
|
174
|
+
entry_points = importlib.metadata.entry_points()
|
|
175
|
+
if hasattr(entry_points, 'select'):
|
|
176
|
+
cli_entries = entry_points.select(group='erispulse.cli')
|
|
177
|
+
else:
|
|
178
|
+
cli_entries = entry_points.get('erispulse.cli', [])
|
|
179
|
+
|
|
180
|
+
for entry in cli_entries:
|
|
181
|
+
dist = entry.dist
|
|
182
|
+
packages["cli_extensions"][entry.name] = {
|
|
183
|
+
"package": dist.metadata["Name"],
|
|
184
|
+
"version": dist.version,
|
|
185
|
+
"summary": dist.metadata["Summary"]
|
|
186
|
+
}
|
|
187
|
+
|
|
151
188
|
except Exception as e:
|
|
152
|
-
console.print(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
style="error"
|
|
156
|
-
))
|
|
189
|
+
console.print(f"[error]获取已安装包信息失败: {e}[/]")
|
|
190
|
+
import traceback
|
|
191
|
+
console.print(traceback.format_exc())
|
|
157
192
|
|
|
158
193
|
return packages
|
|
159
194
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
"""
|
|
163
|
-
优先使用uv安装包
|
|
164
|
-
|
|
165
|
-
:param package_name: str 要安装的包名
|
|
166
|
-
:param upgrade: bool 是否升级已安装的包 (默认: False)
|
|
167
|
-
:return: bool 安装是否成功
|
|
168
|
-
"""
|
|
195
|
+
def _is_module_enabled(self, module_name: str) -> bool:
|
|
196
|
+
"""检查模块是否启用"""
|
|
169
197
|
try:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
cmd = ["uv", "pip", "install"]
|
|
176
|
-
if upgrade:
|
|
177
|
-
cmd.append("--upgrade")
|
|
178
|
-
cmd.append(package_name)
|
|
179
|
-
|
|
180
|
-
with console.status(f"使用uv安装 {package_name}..."):
|
|
181
|
-
result = subprocess.run(cmd, check=True)
|
|
182
|
-
return result.returncode == 0
|
|
183
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
198
|
+
from ErisPulse.Core import mods
|
|
199
|
+
return mods.get_module_status(module_name)
|
|
200
|
+
except ImportError:
|
|
201
|
+
return True
|
|
202
|
+
except Exception:
|
|
184
203
|
return False
|
|
185
204
|
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
def _run_pip_command(self, args: List[str], description: str) -> bool:
|
|
206
|
+
"""运行pip命令并显示进度"""
|
|
207
|
+
with Progress(
|
|
208
|
+
TextColumn(f"[progress.description]{description}"),
|
|
209
|
+
BarColumn(complete_style="progress.download"),
|
|
210
|
+
transient=True
|
|
211
|
+
) as progress:
|
|
212
|
+
task = progress.add_task("", total=100)
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
process = subprocess.Popen(
|
|
216
|
+
[sys.executable, "-m", "pip"] + args,
|
|
217
|
+
stdout=subprocess.PIPE,
|
|
218
|
+
stderr=subprocess.PIPE,
|
|
219
|
+
universal_newlines=True
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
while True:
|
|
223
|
+
output = process.stdout.readline()
|
|
224
|
+
if output == '' and process.poll() is not None:
|
|
225
|
+
break
|
|
226
|
+
if output:
|
|
227
|
+
progress.update(task, advance=1)
|
|
228
|
+
|
|
229
|
+
return process.returncode == 0
|
|
230
|
+
except subprocess.CalledProcessError as e:
|
|
231
|
+
console.print(f"[error]命令执行失败: {e}[/]")
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
def install_package(self, package_name: str, upgrade: bool = False) -> bool:
|
|
188
235
|
"""
|
|
189
|
-
安装指定包
|
|
236
|
+
安装指定包
|
|
190
237
|
|
|
191
|
-
:param package_name:
|
|
192
|
-
:param upgrade:
|
|
193
|
-
:return:
|
|
238
|
+
:param package_name: 要安装的包名
|
|
239
|
+
:param upgrade: 是否升级已安装的包
|
|
240
|
+
:return: 安装是否成功
|
|
194
241
|
"""
|
|
195
|
-
|
|
196
|
-
if
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
242
|
+
cmd = ["install"]
|
|
243
|
+
if upgrade:
|
|
244
|
+
cmd.append("--upgrade")
|
|
245
|
+
cmd.append(package_name)
|
|
246
|
+
|
|
247
|
+
success = self._run_pip_command(cmd, f"安装 {package_name}")
|
|
248
|
+
|
|
249
|
+
if success:
|
|
250
|
+
console.print(f"[success]包 {package_name} 安装成功[/]")
|
|
251
|
+
else:
|
|
252
|
+
console.print(f"[error]包 {package_name} 安装失败[/]")
|
|
203
253
|
|
|
204
|
-
|
|
205
|
-
try:
|
|
206
|
-
cmd = [sys.executable, "-m", "pip", "install"]
|
|
207
|
-
if upgrade:
|
|
208
|
-
cmd.append("--upgrade")
|
|
209
|
-
cmd.append(package_name)
|
|
210
|
-
|
|
211
|
-
with console.status(f"使用pip安装 {package_name}..."):
|
|
212
|
-
result = subprocess.run(cmd, check=True)
|
|
213
|
-
if result.returncode == 0:
|
|
214
|
-
console.print(Panel(
|
|
215
|
-
f"包 {package_name} 安装成功",
|
|
216
|
-
title="成功",
|
|
217
|
-
style="success"
|
|
218
|
-
))
|
|
219
|
-
return True
|
|
220
|
-
return False
|
|
221
|
-
except subprocess.CalledProcessError as e:
|
|
222
|
-
console.print(Panel(
|
|
223
|
-
f"安装包 {package_name} 失败: {e}",
|
|
224
|
-
title="错误",
|
|
225
|
-
style="error"
|
|
226
|
-
))
|
|
227
|
-
return False
|
|
254
|
+
return success
|
|
228
255
|
|
|
229
|
-
|
|
230
|
-
def uninstall_package(package_name: str) -> bool:
|
|
256
|
+
def uninstall_package(self, package_name: str) -> bool:
|
|
231
257
|
"""
|
|
232
258
|
卸载指定包
|
|
233
259
|
|
|
234
|
-
:param package_name:
|
|
235
|
-
:return:
|
|
260
|
+
:param package_name: 要卸载的包名
|
|
261
|
+
:return: 卸载是否成功
|
|
236
262
|
"""
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
))
|
|
249
|
-
return True
|
|
250
|
-
return False
|
|
251
|
-
except subprocess.CalledProcessError as e:
|
|
252
|
-
console.print(Panel(
|
|
253
|
-
f"卸载包 {package_name} 失败: {e}",
|
|
254
|
-
title="错误",
|
|
255
|
-
style="error"
|
|
256
|
-
))
|
|
257
|
-
return False
|
|
263
|
+
success = self._run_pip_command(
|
|
264
|
+
["uninstall", "-y", package_name],
|
|
265
|
+
f"卸载 {package_name}"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if success:
|
|
269
|
+
console.print(f"[success]包 {package_name} 卸载成功[/]")
|
|
270
|
+
else:
|
|
271
|
+
console.print(f"[error]包 {package_name} 卸载失败[/]")
|
|
272
|
+
|
|
273
|
+
return success
|
|
258
274
|
|
|
259
|
-
|
|
260
|
-
def upgrade_all() -> bool:
|
|
275
|
+
def upgrade_all(self) -> bool:
|
|
261
276
|
"""
|
|
262
277
|
升级所有已安装的ErisPulse包
|
|
263
278
|
|
|
264
|
-
:return:
|
|
279
|
+
:return: 升级是否成功
|
|
265
280
|
"""
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
"没有找到可升级的ErisPulse包",
|
|
277
|
-
title="提示",
|
|
278
|
-
style="info"
|
|
279
|
-
))
|
|
280
|
-
return False
|
|
281
|
-
|
|
282
|
-
console.print(Panel(
|
|
283
|
-
f"找到 {len(all_packages)} 个可升级的包:\n" +
|
|
284
|
-
"\n".join(f" - {pkg}" for pkg in all_packages),
|
|
285
|
-
title="升级列表",
|
|
286
|
-
style="info"
|
|
287
|
-
))
|
|
281
|
+
installed = self.get_installed_packages()
|
|
282
|
+
all_packages = set()
|
|
283
|
+
|
|
284
|
+
for pkg_type in ["modules", "adapters", "cli_extensions"]:
|
|
285
|
+
for pkg_info in installed[pkg_type].values():
|
|
286
|
+
all_packages.add(pkg_info["package"])
|
|
287
|
+
|
|
288
|
+
if not all_packages:
|
|
289
|
+
console.print("[info]没有找到可升级的ErisPulse包[/]")
|
|
290
|
+
return False
|
|
288
291
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
console.print(Panel(
|
|
293
|
+
f"找到 [bold]{len(all_packages)}[/] 个可升级的包:\n" +
|
|
294
|
+
"\n".join(f" - [package]{pkg}[/]" for pkg in sorted(all_packages)),
|
|
295
|
+
title="升级列表"
|
|
296
|
+
))
|
|
297
|
+
|
|
298
|
+
if not Confirm.ask("确认升级所有包吗?", default=False):
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
results = {}
|
|
302
|
+
for pkg in sorted(all_packages):
|
|
303
|
+
results[pkg] = self.install_package(pkg, upgrade=True)
|
|
304
|
+
|
|
305
|
+
failed = [pkg for pkg, success in results.items() if not success]
|
|
306
|
+
if failed:
|
|
297
307
|
console.print(Panel(
|
|
298
|
-
f"
|
|
299
|
-
title="
|
|
300
|
-
style="
|
|
308
|
+
f"以下包升级失败:\n" + "\n".join(f" - [error]{pkg}[/]" for pkg in failed),
|
|
309
|
+
title="警告",
|
|
310
|
+
style="warning"
|
|
301
311
|
))
|
|
302
312
|
return False
|
|
313
|
+
|
|
314
|
+
return True
|
|
303
315
|
|
|
304
316
|
class ReloadHandler(FileSystemEventHandler):
|
|
305
317
|
"""
|
|
306
318
|
热重载处理器
|
|
307
319
|
|
|
308
|
-
|
|
320
|
+
监控文件变化并自动重启入口
|
|
309
321
|
"""
|
|
310
|
-
def __init__(self, script_path, reload_mode=False
|
|
311
|
-
super().__init__(
|
|
312
|
-
self.script_path = script_path
|
|
322
|
+
def __init__(self, script_path: str, reload_mode: bool = False):
|
|
323
|
+
super().__init__()
|
|
324
|
+
self.script_path = os.path.abspath(script_path)
|
|
313
325
|
self.process = None
|
|
314
326
|
self.last_reload = time.time()
|
|
315
327
|
self.reload_mode = reload_mode
|
|
316
328
|
self.start_process()
|
|
329
|
+
self.watched_files = set()
|
|
317
330
|
|
|
318
331
|
def start_process(self):
|
|
319
|
-
"""
|
|
320
|
-
启动/重启被监控的进程
|
|
321
|
-
"""
|
|
332
|
+
"""启动/重启被监控的进程"""
|
|
322
333
|
if self.process:
|
|
334
|
+
self._terminate_process()
|
|
335
|
+
|
|
336
|
+
console.print(f"[bold]启动进程: [path]{self.script_path}[/][/]")
|
|
337
|
+
try:
|
|
338
|
+
self.process = subprocess.Popen(
|
|
339
|
+
[sys.executable, self.script_path],
|
|
340
|
+
stdin=sys.stdin,
|
|
341
|
+
stdout=sys.stdout,
|
|
342
|
+
stderr=sys.stderr
|
|
343
|
+
)
|
|
344
|
+
self.last_reload = time.time()
|
|
345
|
+
except Exception as e:
|
|
346
|
+
console.print(f"[error]启动进程失败: {e}[/]")
|
|
347
|
+
raise
|
|
348
|
+
|
|
349
|
+
def _terminate_process(self):
|
|
350
|
+
"""安全终止当前进程"""
|
|
351
|
+
try:
|
|
323
352
|
self.process.terminate()
|
|
353
|
+
# 等待最多2秒让进程正常退出
|
|
354
|
+
self.process.wait(timeout=2)
|
|
355
|
+
except subprocess.TimeoutExpired:
|
|
356
|
+
console.print("[warning]进程未正常退出,强制终止...[/]")
|
|
357
|
+
self.process.kill()
|
|
324
358
|
self.process.wait()
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
self.process = subprocess.Popen([sys.executable, self.script_path])
|
|
328
|
-
self.last_reload = time.time()
|
|
359
|
+
except Exception as e:
|
|
360
|
+
console.print(f"[error]终止进程时出错: {e}[/]")
|
|
329
361
|
|
|
330
362
|
def on_modified(self, event):
|
|
331
|
-
"""
|
|
332
|
-
文件修改事件处理
|
|
333
|
-
|
|
334
|
-
:param event: FileSystemEvent 文件系统事件对象
|
|
335
|
-
"""
|
|
363
|
+
"""文件修改事件处理"""
|
|
336
364
|
now = time.time()
|
|
337
|
-
if now - self.last_reload < 1.0:
|
|
365
|
+
if now - self.last_reload < 1.0: # 防抖
|
|
338
366
|
return
|
|
339
367
|
|
|
340
368
|
if event.src_path.endswith(".py") and self.reload_mode:
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
console.print(f"\n[cyan][热重载] 配置发生变动: {event.src_path}[/]")
|
|
345
|
-
self.start_process()
|
|
369
|
+
self._handle_reload(event, "文件变动")
|
|
370
|
+
elif event.src_path.endswith(("config.toml", ".env")):
|
|
371
|
+
self._handle_reload(event, "配置变动")
|
|
346
372
|
|
|
347
|
-
def
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
:param reload_mode: bool 是否启用完整重载模式 (默认: False)
|
|
353
|
-
"""
|
|
354
|
-
if not os.path.exists(script_path):
|
|
355
|
-
console.print(Panel(
|
|
356
|
-
f"找不到指定文件: {script_path}",
|
|
357
|
-
title="错误",
|
|
358
|
-
style="error"
|
|
359
|
-
))
|
|
360
|
-
return
|
|
361
|
-
watch_dirs = [
|
|
362
|
-
os.path.dirname(os.path.abspath(script_path)),
|
|
363
|
-
]
|
|
364
|
-
|
|
365
|
-
handler = ReloadHandler(script_path, reload_mode)
|
|
366
|
-
observer = Observer()
|
|
367
|
-
|
|
368
|
-
for d in watch_dirs:
|
|
369
|
-
if os.path.exists(d):
|
|
370
|
-
if reload_mode:
|
|
371
|
-
# 完整重载模式:监控所有.py文件
|
|
372
|
-
observer.schedule(handler, d, recursive=True)
|
|
373
|
-
else:
|
|
374
|
-
# 普通模式:只监控config.toml
|
|
375
|
-
observer.schedule(handler, d, recursive=False)
|
|
376
|
-
|
|
377
|
-
observer.start()
|
|
378
|
-
console.print("\n[bold green][热重载] 已启动[/]")
|
|
379
|
-
mode_desc = "开发重载模式" if reload_mode else "配置监控模式"
|
|
380
|
-
console.print(f"[dim]模式: {mode_desc}\n监控目录: {', '.join(watch_dirs)}[/]\n")
|
|
381
|
-
try:
|
|
382
|
-
first_interrupt = True
|
|
383
|
-
while True:
|
|
384
|
-
time.sleep(1)
|
|
385
|
-
except KeyboardInterrupt:
|
|
386
|
-
if first_interrupt:
|
|
387
|
-
first_interrupt = False
|
|
388
|
-
console.print("\n[bold green]正在安全关闭热重载...[/]")
|
|
389
|
-
console.print("[bold red]再次按下Ctrl+C以强制关闭[/]")
|
|
390
|
-
try:
|
|
391
|
-
observer.stop()
|
|
392
|
-
if handler.process:
|
|
393
|
-
handler.process.terminate()
|
|
394
|
-
observer.join()
|
|
395
|
-
except KeyboardInterrupt:
|
|
396
|
-
console.print("[bold red]强制关闭热重载[/]")
|
|
397
|
-
raise
|
|
398
|
-
else:
|
|
399
|
-
console.print(Panel("[bold red]强制关闭热重载[/]"))
|
|
400
|
-
raise
|
|
401
|
-
|
|
402
|
-
def get_erispulse_version():
|
|
403
|
-
"""
|
|
404
|
-
获取当前安装的ErisPulse版本
|
|
405
|
-
|
|
406
|
-
:return: str ErisPulse版本号或"unknown version"
|
|
407
|
-
"""
|
|
408
|
-
try:
|
|
409
|
-
return version("ErisPulse")
|
|
410
|
-
except PackageNotFoundError:
|
|
411
|
-
return "unknown version"
|
|
373
|
+
def _handle_reload(self, event, reason: str):
|
|
374
|
+
"""处理重载逻辑"""
|
|
375
|
+
console.print(f"\n[reload]{reason}: [path]{event.src_path}[/][/]")
|
|
376
|
+
self._terminate_process()
|
|
377
|
+
self.start_process()
|
|
412
378
|
|
|
413
|
-
|
|
414
|
-
"""
|
|
415
|
-
CLI主入口
|
|
379
|
+
class CLI:
|
|
380
|
+
"""主CLI应用程序"""
|
|
416
381
|
|
|
417
|
-
|
|
418
|
-
|
|
382
|
+
def __init__(self):
|
|
383
|
+
self.parser = self._create_parser()
|
|
384
|
+
self.package_manager = PackageManager()
|
|
385
|
+
self.observer = None
|
|
386
|
+
self.handler = None
|
|
387
|
+
|
|
388
|
+
def _create_parser(self) -> argparse.ArgumentParser:
|
|
389
|
+
"""创建参数解析器"""
|
|
390
|
+
parser = argparse.ArgumentParser(
|
|
391
|
+
prog="epsdk",
|
|
392
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
393
|
+
description="ErisPulse SDK 命令行工具\n\n一个功能强大的模块化系统管理工具,用于管理ErisPulse生态系统中的模块、适配器和扩展。",
|
|
394
|
+
)
|
|
395
|
+
parser._positionals.title = "命令"
|
|
396
|
+
parser._optionals.title = "选项"
|
|
397
|
+
|
|
398
|
+
# 全局选项
|
|
399
|
+
parser.add_argument(
|
|
400
|
+
"--version", "-V",
|
|
401
|
+
action="store_true",
|
|
402
|
+
help="显示版本信息"
|
|
403
|
+
)
|
|
404
|
+
parser.add_argument(
|
|
405
|
+
"--verbose", "-v",
|
|
406
|
+
action="count",
|
|
407
|
+
default=0,
|
|
408
|
+
help="增加输出详细程度 (-v, -vv, -vvv)"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# 子命令
|
|
412
|
+
subparsers = parser.add_subparsers(
|
|
413
|
+
dest='command',
|
|
414
|
+
metavar="<命令>",
|
|
415
|
+
help="要执行的操作"
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# 安装命令
|
|
419
|
+
install_parser = subparsers.add_parser(
|
|
420
|
+
'install',
|
|
421
|
+
help='安装模块/适配器包'
|
|
422
|
+
)
|
|
423
|
+
install_parser.add_argument(
|
|
424
|
+
'package',
|
|
425
|
+
help='要安装的包名或模块/适配器简称'
|
|
426
|
+
)
|
|
427
|
+
install_parser.add_argument(
|
|
428
|
+
'--upgrade', '-U',
|
|
429
|
+
action='store_true',
|
|
430
|
+
help='升级已安装的包'
|
|
431
|
+
)
|
|
432
|
+
install_parser.add_argument(
|
|
433
|
+
'--pre',
|
|
434
|
+
action='store_true',
|
|
435
|
+
help='包含预发布版本'
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# 卸载命令
|
|
439
|
+
uninstall_parser = subparsers.add_parser(
|
|
440
|
+
'uninstall',
|
|
441
|
+
help='卸载模块/适配器包'
|
|
442
|
+
)
|
|
443
|
+
uninstall_parser.add_argument(
|
|
444
|
+
'package',
|
|
445
|
+
help='要卸载的包名'
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# 模块管理命令
|
|
449
|
+
module_parser = subparsers.add_parser(
|
|
450
|
+
'module',
|
|
451
|
+
help='模块管理'
|
|
452
|
+
)
|
|
453
|
+
module_subparsers = module_parser.add_subparsers(
|
|
454
|
+
dest='module_command',
|
|
455
|
+
metavar="<子命令>"
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
# 启用模块
|
|
459
|
+
enable_parser = module_subparsers.add_parser(
|
|
460
|
+
'enable',
|
|
461
|
+
help='启用模块'
|
|
462
|
+
)
|
|
463
|
+
enable_parser.add_argument(
|
|
464
|
+
'module',
|
|
465
|
+
help='要启用的模块名'
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# 禁用模块
|
|
469
|
+
disable_parser = module_subparsers.add_parser(
|
|
470
|
+
'disable',
|
|
471
|
+
help='禁用模块'
|
|
472
|
+
)
|
|
473
|
+
disable_parser.add_argument(
|
|
474
|
+
'module',
|
|
475
|
+
help='要禁用的模块名'
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# 列表命令
|
|
479
|
+
list_parser = subparsers.add_parser(
|
|
480
|
+
'list',
|
|
481
|
+
help='列出已安装的组件'
|
|
482
|
+
)
|
|
483
|
+
list_parser.add_argument(
|
|
484
|
+
'--type', '-t',
|
|
485
|
+
choices=['modules', 'adapters', 'cli', 'all'],
|
|
486
|
+
default='all',
|
|
487
|
+
help='列出类型 (默认: all)'
|
|
488
|
+
)
|
|
489
|
+
list_parser.add_argument(
|
|
490
|
+
'--outdated', '-o',
|
|
491
|
+
action='store_true',
|
|
492
|
+
help='仅显示可升级的包'
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# 远程列表命令
|
|
496
|
+
list_remote_parser = subparsers.add_parser(
|
|
497
|
+
'list-remote',
|
|
498
|
+
help='列出远程可用的组件'
|
|
499
|
+
)
|
|
500
|
+
list_remote_parser.add_argument(
|
|
501
|
+
'--type', '-t',
|
|
502
|
+
choices=['modules', 'adapters', 'cli', 'all'],
|
|
503
|
+
default='all',
|
|
504
|
+
help='列出类型 (默认: all)'
|
|
505
|
+
)
|
|
506
|
+
list_remote_parser.add_argument(
|
|
507
|
+
'--refresh', '-r',
|
|
508
|
+
action='store_true',
|
|
509
|
+
help='强制刷新远程包列表'
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
# 升级命令
|
|
513
|
+
upgrade_parser = subparsers.add_parser(
|
|
514
|
+
'upgrade',
|
|
515
|
+
help='升级组件'
|
|
516
|
+
)
|
|
517
|
+
upgrade_parser.add_argument(
|
|
518
|
+
'package',
|
|
519
|
+
nargs='?',
|
|
520
|
+
help='要升级的包名 (可选,不指定则升级所有)'
|
|
521
|
+
)
|
|
522
|
+
upgrade_parser.add_argument(
|
|
523
|
+
'--force', '-f',
|
|
524
|
+
action='store_true',
|
|
525
|
+
help='跳过确认直接升级'
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# 运行命令
|
|
529
|
+
run_parser = subparsers.add_parser(
|
|
530
|
+
'run',
|
|
531
|
+
help='运行主程序'
|
|
532
|
+
)
|
|
533
|
+
run_parser.add_argument(
|
|
534
|
+
'script',
|
|
535
|
+
nargs='?',
|
|
536
|
+
help='要运行的主程序路径 (默认: main.py)'
|
|
537
|
+
)
|
|
538
|
+
run_parser.add_argument(
|
|
539
|
+
'--reload',
|
|
540
|
+
action='store_true',
|
|
541
|
+
help='启用热重载模式'
|
|
542
|
+
)
|
|
543
|
+
run_parser.add_argument(
|
|
544
|
+
'--no-reload',
|
|
545
|
+
action='store_true',
|
|
546
|
+
help='禁用热重载模式'
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
# 初始化命令
|
|
550
|
+
init_parser = subparsers.add_parser(
|
|
551
|
+
'init',
|
|
552
|
+
help='初始化ErisPulse项目'
|
|
553
|
+
)
|
|
554
|
+
init_parser.add_argument(
|
|
555
|
+
'--force', '-f',
|
|
556
|
+
action='store_true',
|
|
557
|
+
help='强制覆盖现有配置'
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
# 加载第三方命令
|
|
561
|
+
self._load_external_commands(subparsers)
|
|
562
|
+
|
|
563
|
+
return parser
|
|
419
564
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
565
|
+
def _load_external_commands(self, subparsers):
|
|
566
|
+
"""加载第三方CLI命令"""
|
|
567
|
+
try:
|
|
568
|
+
entry_points = importlib.metadata.entry_points()
|
|
569
|
+
if hasattr(entry_points, 'select'):
|
|
570
|
+
cli_entries = entry_points.select(group='erispulse.cli')
|
|
571
|
+
else:
|
|
572
|
+
cli_entries = entry_points.get('erispulse.cli', [])
|
|
573
|
+
|
|
574
|
+
for entry in cli_entries:
|
|
575
|
+
try:
|
|
576
|
+
cli_func = entry.load()
|
|
577
|
+
if callable(cli_func):
|
|
578
|
+
cli_func(subparsers, console)
|
|
579
|
+
else:
|
|
580
|
+
console.print(f"[warning]模块 {entry.name} 的入口点不是可调用对象[/]")
|
|
581
|
+
except Exception as e:
|
|
582
|
+
console.print(f"[error]加载第三方命令 {entry.name} 失败: {e}[/]")
|
|
583
|
+
except Exception as e:
|
|
584
|
+
console.print(f"[warning]加载第三方CLI命令失败: {e}[/]")
|
|
427
585
|
|
|
428
|
-
|
|
586
|
+
def _print_version(self):
|
|
587
|
+
"""打印版本信息"""
|
|
588
|
+
from ErisPulse import __version__
|
|
589
|
+
console.print(Panel(
|
|
590
|
+
f"[title]ErisPulse SDK[/] 版本: [bold]{__version__}[/]",
|
|
591
|
+
subtitle=f"Python {sys.version.split()[0]}",
|
|
592
|
+
style="title"
|
|
593
|
+
))
|
|
429
594
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
# 在main()函数中修改第三方命令加载部分
|
|
436
|
-
try:
|
|
437
|
-
entry_points = importlib.metadata.entry_points()
|
|
438
|
-
if hasattr(entry_points, 'select'):
|
|
439
|
-
cli_entries = entry_points.select(group='erispulse.cli')
|
|
440
|
-
else:
|
|
441
|
-
cli_entries = entry_points.get('erispulse.cli', [])
|
|
595
|
+
def _print_installed_packages(self, pkg_type: str, outdated_only: bool = False):
|
|
596
|
+
"""打印已安装的包列表"""
|
|
597
|
+
installed = self.package_manager.get_installed_packages()
|
|
442
598
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
599
|
+
if pkg_type == "modules" and installed["modules"]:
|
|
600
|
+
table = Table(
|
|
601
|
+
title="已安装模块",
|
|
602
|
+
box=SIMPLE,
|
|
603
|
+
header_style="module"
|
|
604
|
+
)
|
|
605
|
+
table.add_column("模块名", style="module")
|
|
606
|
+
table.add_column("包名")
|
|
607
|
+
table.add_column("版本")
|
|
608
|
+
table.add_column("状态")
|
|
609
|
+
table.add_column("描述")
|
|
610
|
+
|
|
611
|
+
for name, info in installed["modules"].items():
|
|
612
|
+
if outdated_only and not self._is_package_outdated(info["package"], info["version"]):
|
|
613
|
+
continue
|
|
614
|
+
|
|
615
|
+
status = "[green]已启用[/]" if info.get("enabled", True) else "[yellow]已禁用[/]"
|
|
616
|
+
table.add_row(
|
|
617
|
+
name,
|
|
618
|
+
info["package"],
|
|
619
|
+
info["version"],
|
|
620
|
+
status,
|
|
621
|
+
info["summary"]
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
console.print(table)
|
|
625
|
+
|
|
626
|
+
if pkg_type == "adapters" and installed["adapters"]:
|
|
627
|
+
table = Table(
|
|
628
|
+
title="已安装适配器",
|
|
629
|
+
box=SIMPLE,
|
|
630
|
+
header_style="adapter"
|
|
631
|
+
)
|
|
632
|
+
table.add_column("适配器名", style="adapter")
|
|
633
|
+
table.add_column("包名")
|
|
634
|
+
table.add_column("版本")
|
|
635
|
+
table.add_column("描述")
|
|
636
|
+
|
|
637
|
+
for name, info in installed["adapters"].items():
|
|
638
|
+
if outdated_only and not self._is_package_outdated(info["package"], info["version"]):
|
|
639
|
+
continue
|
|
640
|
+
|
|
641
|
+
table.add_row(
|
|
642
|
+
name,
|
|
643
|
+
info["package"],
|
|
644
|
+
info["version"],
|
|
645
|
+
info["summary"]
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
console.print(table)
|
|
649
|
+
|
|
650
|
+
if pkg_type == "cli" and installed["cli_extensions"]:
|
|
651
|
+
table = Table(
|
|
652
|
+
title="已安装CLI扩展",
|
|
653
|
+
box=SIMPLE,
|
|
654
|
+
header_style="cli"
|
|
655
|
+
)
|
|
656
|
+
table.add_column("命令名", style="cli")
|
|
657
|
+
table.add_column("包名")
|
|
658
|
+
table.add_column("版本")
|
|
659
|
+
table.add_column("描述")
|
|
660
|
+
|
|
661
|
+
for name, info in installed["cli_extensions"].items():
|
|
662
|
+
if outdated_only and not self._is_package_outdated(info["package"], info["version"]):
|
|
663
|
+
continue
|
|
664
|
+
|
|
665
|
+
table.add_row(
|
|
666
|
+
name,
|
|
667
|
+
info["package"],
|
|
668
|
+
info["version"],
|
|
669
|
+
info["summary"]
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
console.print(table)
|
|
462
673
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
674
|
+
def _print_remote_packages(self, pkg_type: str):
|
|
675
|
+
"""打印远程包列表"""
|
|
676
|
+
remote_packages = asyncio.run(self.package_manager.get_remote_packages())
|
|
677
|
+
|
|
678
|
+
if pkg_type == "modules" and remote_packages["modules"]:
|
|
679
|
+
table = Table(
|
|
680
|
+
title="远程模块",
|
|
681
|
+
box=SIMPLE,
|
|
682
|
+
header_style="module"
|
|
683
|
+
)
|
|
684
|
+
table.add_column("模块名", style="module")
|
|
685
|
+
table.add_column("包名")
|
|
686
|
+
table.add_column("最新版本")
|
|
687
|
+
table.add_column("描述")
|
|
688
|
+
|
|
689
|
+
for name, info in remote_packages["modules"].items():
|
|
690
|
+
table.add_row(
|
|
691
|
+
name,
|
|
692
|
+
info["package"],
|
|
693
|
+
info["version"],
|
|
694
|
+
info["description"]
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
console.print(table)
|
|
698
|
+
|
|
699
|
+
if pkg_type == "adapters" and remote_packages["adapters"]:
|
|
700
|
+
table = Table(
|
|
701
|
+
title="远程适配器",
|
|
702
|
+
box=SIMPLE,
|
|
703
|
+
header_style="adapter"
|
|
704
|
+
)
|
|
705
|
+
table.add_column("适配器名", style="adapter")
|
|
706
|
+
table.add_column("包名")
|
|
707
|
+
table.add_column("最新版本")
|
|
708
|
+
table.add_column("描述")
|
|
709
|
+
|
|
710
|
+
for name, info in remote_packages["adapters"].items():
|
|
711
|
+
table.add_row(
|
|
712
|
+
name,
|
|
713
|
+
info["package"],
|
|
714
|
+
info["version"],
|
|
715
|
+
info["description"]
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
console.print(table)
|
|
719
|
+
|
|
720
|
+
if pkg_type == "cli" and remote_packages.get("cli_extensions"):
|
|
721
|
+
table = Table(
|
|
722
|
+
title="远程CLI扩展",
|
|
723
|
+
box=SIMPLE,
|
|
724
|
+
header_style="cli"
|
|
725
|
+
)
|
|
726
|
+
table.add_column("命令名", style="cli")
|
|
727
|
+
table.add_column("包名")
|
|
728
|
+
table.add_column("最新版本")
|
|
729
|
+
table.add_column("描述")
|
|
730
|
+
|
|
731
|
+
for name, info in remote_packages["cli_extensions"].items():
|
|
732
|
+
table.add_row(
|
|
733
|
+
name,
|
|
734
|
+
info["package"],
|
|
735
|
+
info["version"],
|
|
736
|
+
info["description"]
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
console.print(table)
|
|
466
740
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
741
|
+
def _is_package_outdated(self, package_name: str, current_version: str) -> bool:
|
|
742
|
+
"""检查包是否有更新"""
|
|
743
|
+
remote_packages = asyncio.run(self.package_manager.get_remote_packages())
|
|
744
|
+
|
|
745
|
+
# 检查模块
|
|
746
|
+
for module_info in remote_packages["modules"].values():
|
|
747
|
+
if module_info["package"] == package_name:
|
|
748
|
+
return module_info["version"] != current_version
|
|
749
|
+
|
|
750
|
+
# 检查适配器
|
|
751
|
+
for adapter_info in remote_packages["adapters"].values():
|
|
752
|
+
if adapter_info["package"] == package_name:
|
|
753
|
+
return adapter_info["version"] != current_version
|
|
754
|
+
|
|
755
|
+
# 检查CLI扩展
|
|
756
|
+
for cli_info in remote_packages.get("cli_extensions", {}).values():
|
|
757
|
+
if cli_info["package"] == package_name:
|
|
758
|
+
return cli_info["version"] != current_version
|
|
759
|
+
|
|
760
|
+
return False
|
|
479
761
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
762
|
+
def _resolve_package_name(self, short_name: str) -> Optional[str]:
|
|
763
|
+
"""解析模块/适配器简称到完整包名"""
|
|
764
|
+
remote_packages = asyncio.run(self.package_manager.get_remote_packages())
|
|
765
|
+
|
|
766
|
+
# 检查模块
|
|
767
|
+
if short_name in remote_packages["modules"]:
|
|
768
|
+
return remote_packages["modules"][short_name]["package"]
|
|
769
|
+
|
|
770
|
+
# 检查适配器
|
|
771
|
+
if short_name in remote_packages["adapters"]:
|
|
772
|
+
return remote_packages["adapters"][short_name]["package"]
|
|
773
|
+
|
|
774
|
+
return None
|
|
487
775
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
776
|
+
def _setup_watchdog(self, script_path: str, reload_mode: bool):
|
|
777
|
+
"""设置文件监控"""
|
|
778
|
+
watch_dirs = [
|
|
779
|
+
os.path.dirname(os.path.abspath(script_path)),
|
|
780
|
+
]
|
|
781
|
+
|
|
782
|
+
# 添加配置目录
|
|
783
|
+
config_dir = os.path.abspath(os.getcwd())
|
|
784
|
+
if config_dir not in watch_dirs:
|
|
785
|
+
watch_dirs.append(config_dir)
|
|
786
|
+
|
|
787
|
+
self.handler = ReloadHandler(script_path, reload_mode)
|
|
788
|
+
self.observer = Observer()
|
|
789
|
+
|
|
790
|
+
for d in watch_dirs:
|
|
791
|
+
if os.path.exists(d):
|
|
792
|
+
self.observer.schedule(
|
|
793
|
+
self.handler,
|
|
794
|
+
d,
|
|
795
|
+
recursive=reload_mode
|
|
796
|
+
)
|
|
797
|
+
console.print(f"[dim]监控目录: [path]{d}[/][/]")
|
|
798
|
+
|
|
799
|
+
self.observer.start()
|
|
800
|
+
|
|
801
|
+
mode_desc = "[bold]开发重载模式[/]" if reload_mode else "[bold]配置监控模式[/]"
|
|
802
|
+
console.print(Panel(
|
|
803
|
+
f"{mode_desc}\n监控目录: [path]{', '.join(watch_dirs)}[/]",
|
|
804
|
+
title="热重载已启动",
|
|
805
|
+
border_style="info"
|
|
806
|
+
))
|
|
492
807
|
|
|
493
|
-
|
|
808
|
+
def _cleanup(self):
|
|
809
|
+
"""清理资源"""
|
|
810
|
+
if self.observer:
|
|
811
|
+
self.observer.stop()
|
|
812
|
+
if self.handler and self.handler.process:
|
|
813
|
+
self.handler._terminate_process()
|
|
814
|
+
self.observer.join()
|
|
494
815
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
816
|
+
def run(self):
|
|
817
|
+
"""运行CLI应用程序"""
|
|
818
|
+
args = self.parser.parse_args()
|
|
498
819
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
# 检查模块
|
|
512
|
-
if args.package in remote_packages["modules"]:
|
|
513
|
-
full_package_name = remote_packages["modules"][args.package]["package"]
|
|
514
|
-
# 检查适配器
|
|
515
|
-
elif args.package in remote_packages["adapters"]:
|
|
516
|
-
full_package_name = remote_packages["adapters"][args.package]["package"]
|
|
517
|
-
|
|
518
|
-
# 如果找到远程包,使用完整包名安装
|
|
519
|
-
if full_package_name:
|
|
520
|
-
console.print(Panel(
|
|
521
|
-
f"找到远程包: [bold]{args.package}[/] → [blue]{full_package_name}[/]",
|
|
522
|
-
title="信息",
|
|
523
|
-
style="info"
|
|
524
|
-
))
|
|
525
|
-
PyPIManager.install_package(full_package_name, args.upgrade)
|
|
526
|
-
else:
|
|
527
|
-
# 否则按原样安装
|
|
528
|
-
PyPIManager.install_package(args.package, args.upgrade)
|
|
820
|
+
# 设置日志级别
|
|
821
|
+
if args.verbose >= 3:
|
|
822
|
+
import logging
|
|
823
|
+
logging.basicConfig(
|
|
824
|
+
level=logging.DEBUG,
|
|
825
|
+
format="%(message)s",
|
|
826
|
+
handlers=[RichHandler(console=console)]
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
if args.version:
|
|
830
|
+
self._print_version()
|
|
831
|
+
return
|
|
529
832
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
elif args.command == "enable":
|
|
534
|
-
from ErisPulse.Core import mods
|
|
535
|
-
installed = PyPIManager.get_installed_packages()
|
|
536
|
-
if args.module not in installed["modules"]:
|
|
537
|
-
console.print(Panel(
|
|
538
|
-
f"模块 [red]{args.module}[/] 不存在或未安装",
|
|
539
|
-
title="错误",
|
|
540
|
-
style="error"
|
|
541
|
-
))
|
|
542
|
-
else:
|
|
543
|
-
mods.set_module_status(args.module, True)
|
|
544
|
-
console.print(Panel(
|
|
545
|
-
f"模块 [green]{args.module}[/] 已启用",
|
|
546
|
-
title="成功",
|
|
547
|
-
style="success"
|
|
548
|
-
))
|
|
549
|
-
|
|
550
|
-
elif args.command == "disable":
|
|
551
|
-
from ErisPulse.Core import mods
|
|
552
|
-
installed = PyPIManager.get_installed_packages()
|
|
553
|
-
if args.module not in installed["modules"]:
|
|
554
|
-
console.print(Panel(
|
|
555
|
-
f"模块 [red]{args.module}[/] 不存在或未安装",
|
|
556
|
-
title="错误",
|
|
557
|
-
style="error"
|
|
558
|
-
))
|
|
559
|
-
else:
|
|
560
|
-
mods.set_module_status(args.module, False)
|
|
561
|
-
console.print(Panel(
|
|
562
|
-
f"模块 [yellow]{args.module}[/] 已禁用",
|
|
563
|
-
title="成功",
|
|
564
|
-
style="warning"
|
|
565
|
-
))
|
|
566
|
-
|
|
567
|
-
elif args.command == "list":
|
|
568
|
-
installed = PyPIManager.get_installed_packages()
|
|
833
|
+
if not args.command:
|
|
834
|
+
self.parser.print_help()
|
|
835
|
+
return
|
|
569
836
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
837
|
+
try:
|
|
838
|
+
if args.command == "install":
|
|
839
|
+
full_package = self._resolve_package_name(args.package)
|
|
840
|
+
if full_package:
|
|
841
|
+
console.print(f"[info]找到远程包: [bold]{args.package}[/] → [package]{full_package}[/][/]")
|
|
842
|
+
self.package_manager.install_package(full_package, args.upgrade)
|
|
576
843
|
else:
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
for entry in cli_entries:
|
|
580
|
-
dist = entry.dist
|
|
581
|
-
cli_extensions[entry.name] = {
|
|
582
|
-
"package": dist.metadata["Name"],
|
|
583
|
-
"version": dist.version,
|
|
584
|
-
"summary": dist.metadata["Summary"]
|
|
585
|
-
}
|
|
586
|
-
except Exception as e:
|
|
587
|
-
console.print(f"[yellow]获取CLI扩展信息失败: {e}[/]", style="warning")
|
|
588
|
-
|
|
589
|
-
if args.type in ["modules", "all"] and installed["modules"]:
|
|
590
|
-
table = Table(title="已安装模块", box=SIMPLE)
|
|
591
|
-
table.add_column("模块名", style="green")
|
|
592
|
-
table.add_column("包名", style="blue")
|
|
593
|
-
table.add_column("版本")
|
|
594
|
-
table.add_column("描述")
|
|
595
|
-
|
|
596
|
-
for name, info in installed["modules"].items():
|
|
597
|
-
table.add_row(name, info["package"], info["version"], info["summary"])
|
|
598
|
-
|
|
599
|
-
console.print(table)
|
|
600
|
-
|
|
601
|
-
if args.type in ["adapters", "all"] and installed["adapters"]:
|
|
602
|
-
table = Table(title="已安装适配器", box=SIMPLE)
|
|
603
|
-
table.add_column("适配器名", style="yellow")
|
|
604
|
-
table.add_column("包名", style="blue")
|
|
605
|
-
table.add_column("版本")
|
|
606
|
-
table.add_column("描述")
|
|
607
|
-
|
|
608
|
-
for name, info in installed["adapters"].items():
|
|
609
|
-
table.add_row(name, info["package"], info["version"], info["summary"])
|
|
610
|
-
|
|
611
|
-
console.print(table)
|
|
612
|
-
|
|
613
|
-
if cli_extensions and args.type in ["cli", "all"]:
|
|
614
|
-
table = Table(title="已安装CLI扩展", box=SIMPLE)
|
|
615
|
-
table.add_column("命令名", style="magenta")
|
|
616
|
-
table.add_column("包名", style="blue")
|
|
617
|
-
table.add_column("版本")
|
|
618
|
-
table.add_column("描述")
|
|
844
|
+
self.package_manager.install_package(args.package, args.upgrade)
|
|
619
845
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
console.print(table)
|
|
846
|
+
elif args.command == "uninstall":
|
|
847
|
+
self.package_manager.uninstall_package(args.package)
|
|
623
848
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
title="提示",
|
|
628
|
-
style="info"
|
|
629
|
-
))
|
|
849
|
+
elif args.command == "module":
|
|
850
|
+
from ErisPulse.Core import mods
|
|
851
|
+
installed = self.package_manager.get_installed_packages()
|
|
630
852
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
start_reloader(args.script, args.reload)
|
|
647
|
-
elif args.command == "list-remote":
|
|
648
|
-
import asyncio
|
|
649
|
-
try:
|
|
650
|
-
remote_packages = asyncio.run(PyPIManager.get_remote_packages())
|
|
651
|
-
|
|
652
|
-
if args.type in ["modules", "all"] and remote_packages["modules"]:
|
|
653
|
-
table = Table(title="远程模块", box=SIMPLE)
|
|
654
|
-
table.add_column("模块名", style="green")
|
|
655
|
-
table.add_column("包名", style="blue")
|
|
656
|
-
table.add_column("版本")
|
|
657
|
-
table.add_column("描述")
|
|
658
|
-
|
|
659
|
-
for name, info in remote_packages["modules"].items():
|
|
660
|
-
table.add_row(name, info["package"], info["version"], info["description"])
|
|
853
|
+
if args.module_command == "enable":
|
|
854
|
+
if args.module not in installed["modules"]:
|
|
855
|
+
console.print(f"[error]模块 [bold]{args.module}[/] 不存在或未安装[/]")
|
|
856
|
+
else:
|
|
857
|
+
mods.set_module_status(args.module, True)
|
|
858
|
+
console.print(f"[success]模块 [bold]{args.module}[/] 已启用[/]")
|
|
859
|
+
|
|
860
|
+
elif args.module_command == "disable":
|
|
861
|
+
if args.module not in installed["modules"]:
|
|
862
|
+
console.print(f"[error]模块 [bold]{args.module}[/] 不存在或未安装[/]")
|
|
863
|
+
else:
|
|
864
|
+
mods.set_module_status(args.module, False)
|
|
865
|
+
console.print(f"[warning]模块 [bold]{args.module}[/] 已禁用[/]")
|
|
866
|
+
else:
|
|
867
|
+
self.parser.parse_args(["module", "--help"])
|
|
661
868
|
|
|
662
|
-
|
|
869
|
+
elif args.command == "list":
|
|
870
|
+
pkg_type = args.type
|
|
871
|
+
if pkg_type == "all":
|
|
872
|
+
self._print_installed_packages("modules", args.outdated)
|
|
873
|
+
self._print_installed_packages("adapters", args.outdated)
|
|
874
|
+
self._print_installed_packages("cli", args.outdated)
|
|
875
|
+
else:
|
|
876
|
+
self._print_installed_packages(pkg_type, args.outdated)
|
|
663
877
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
878
|
+
elif args.command == "list-remote":
|
|
879
|
+
pkg_type = args.type
|
|
880
|
+
if pkg_type == "all":
|
|
881
|
+
self._print_remote_packages("modules")
|
|
882
|
+
self._print_remote_packages("adapters")
|
|
883
|
+
self._print_remote_packages("cli")
|
|
884
|
+
else:
|
|
885
|
+
self._print_remote_packages(pkg_type)
|
|
670
886
|
|
|
671
|
-
|
|
672
|
-
|
|
887
|
+
elif args.command == "upgrade":
|
|
888
|
+
if args.package:
|
|
889
|
+
self.package_manager.install_package(args.package, upgrade=True)
|
|
890
|
+
else:
|
|
891
|
+
if args.force or Confirm.ask("确定要升级所有ErisPulse组件吗?"):
|
|
892
|
+
self.package_manager.upgrade_all()
|
|
893
|
+
|
|
894
|
+
elif args.command == "run":
|
|
895
|
+
script = args.script or "main.py"
|
|
896
|
+
if not os.path.exists(script):
|
|
897
|
+
console.print(f"[error]找不到指定文件: [path]{script}[/][/]")
|
|
898
|
+
return
|
|
673
899
|
|
|
674
|
-
|
|
900
|
+
reload_mode = args.reload and not args.no_reload
|
|
901
|
+
self._setup_watchdog(script, reload_mode)
|
|
675
902
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
for name, info in remote_packages["cli_extensions"].items():
|
|
685
|
-
commands = info.get("command", [])
|
|
686
|
-
command_str = ", ".join(commands) if isinstance(commands, list) else str(commands)
|
|
687
|
-
table.add_row(command_str, info["package"], info["version"], info["description"])
|
|
688
|
-
|
|
689
|
-
console.print(table)
|
|
690
|
-
|
|
691
|
-
if not remote_packages["modules"] and not remote_packages["adapters"] and \
|
|
692
|
-
(not "cli_extensions" in remote_packages or not remote_packages["cli_extensions"]):
|
|
693
|
-
console.print(Panel(
|
|
694
|
-
"没有找到远程模块、适配器或CLI扩展",
|
|
695
|
-
title="提示",
|
|
696
|
-
style="info"
|
|
697
|
-
))
|
|
903
|
+
try:
|
|
904
|
+
while True:
|
|
905
|
+
time.sleep(1)
|
|
906
|
+
except KeyboardInterrupt:
|
|
907
|
+
console.print("\n[info]正在安全关闭...[/]")
|
|
908
|
+
self._cleanup()
|
|
909
|
+
console.print("[success]已安全退出[/]")
|
|
698
910
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
console.print(
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
)
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
style="error"
|
|
720
|
-
))
|
|
911
|
+
elif args.command == "init":
|
|
912
|
+
from ErisPulse import sdk
|
|
913
|
+
sdk.init(force=args.force)
|
|
914
|
+
console.print("[success]ErisPulse项目初始化完成[/]")
|
|
915
|
+
|
|
916
|
+
except KeyboardInterrupt:
|
|
917
|
+
console.print("\n[warning]操作被用户中断[/]")
|
|
918
|
+
self._cleanup()
|
|
919
|
+
except Exception as e:
|
|
920
|
+
console.print(f"[error]执行命令时出错: {e}[/]")
|
|
921
|
+
if args.verbose >= 1:
|
|
922
|
+
import traceback
|
|
923
|
+
console.print(traceback.format_exc())
|
|
924
|
+
self._cleanup()
|
|
925
|
+
sys.exit(1)
|
|
926
|
+
|
|
927
|
+
def main():
|
|
928
|
+
"""主入口"""
|
|
929
|
+
cli = CLI()
|
|
930
|
+
cli.run()
|
|
721
931
|
|
|
722
932
|
if __name__ == "__main__":
|
|
723
933
|
main()
|