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 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
- console = Console(theme=theme, color_system="auto", force_terminal=True)
70
+ # 全局控制台实例
71
+ console = Console(
72
+ theme=theme,
73
+ color_system="auto",
74
+ force_terminal=True,
75
+ highlighter=CommandHighlighter()
76
+ )
46
77
 
47
- class PyPIManager:
78
+ class PackageManager:
48
79
  """
49
- PyPI包管理器
80
+ ErisPulse包管理器
50
81
 
51
- 负责与PyPI交互,包括搜索、安装、卸载和升级ErisPulse模块/适配器
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
- @staticmethod
59
- async def get_remote_packages() -> dict:
60
- """
61
- 获取远程包列表
89
+ CACHE_EXPIRY = 3600 # 1小时缓存
90
+
91
+ def __init__(self):
92
+ self._cache = {}
93
+ self._cache_time = {}
62
94
 
63
- 从配置的远程源获取所有可用的ErisPulse模块和适配器
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=5)
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(PyPIManager.REMOTE_SOURCES[0]) as response:
103
+ async with session.get(url) as response:
82
104
  if response.status == 200:
83
105
  data = await response.text()
84
- data = json.loads(data)
85
- return {
86
- "modules": data.get("modules", {}),
87
- "adapters": data.get("adapters", {})
88
- }
89
- except (ClientError, asyncio.TimeoutError) as e:
90
- last_error = e
91
- console.print(Panel(
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
- try:
98
- async with aiohttp.ClientSession(timeout=timeout) as session:
99
- async with session.get(PyPIManager.REMOTE_SOURCES[1]) as response:
100
- if response.status == 200:
101
- data = await response.text()
102
- data = json.loads(data)
103
- return {
104
- "modules": data.get("modules", {}),
105
- "adapters": data.get("adapters", {})
106
- }
107
- except (ClientError, asyncio.TimeoutError) as e:
108
- last_error = e
109
-
110
- if last_error:
111
- console.print(Panel(
112
- f"获取远程模块列表失败: {last_error}",
113
- title="错误",
114
- style="error"
115
- ))
116
- return {"modules": {}, "adapters": {}}
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
- @staticmethod
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(Panel(
153
- f"获取已安装包信息失败: {e}",
154
- title="错误",
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
- @staticmethod
161
- def uv_install_package(package_name: str, upgrade: bool = False) -> bool:
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
- # 检查uv是否可用
171
- uv_check = subprocess.run(["uv", "--version"], capture_output=True)
172
- if uv_check.returncode != 0:
173
- return False # uv不可用
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
- @staticmethod
187
- def install_package(package_name: str, upgrade: bool = False) -> bool:
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
- 安装指定包 (修改后优先尝试uv)
236
+ 安装指定包
190
237
 
191
- :param package_name: str 要安装的包名
192
- :param upgrade: bool 是否升级已安装的包 (默认: False)
193
- :return: bool 安装是否成功
238
+ :param package_name: 要安装的包名
239
+ :param upgrade: 是否升级已安装的包
240
+ :return: 安装是否成功
194
241
  """
195
- # 优先尝试uv安装
196
- if PyPIManager.uv_install_package(package_name, upgrade):
197
- console.print(Panel(
198
- f"包 {package_name} 安装成功 (使用uv)",
199
- title="成功",
200
- style="success"
201
- ))
202
- return True
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
- # 回退到pip安装
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
- @staticmethod
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: str 要卸载的包名
235
- :return: bool 卸载是否成功
260
+ :param package_name: 要卸载的包名
261
+ :return: 卸载是否成功
236
262
  """
237
- try:
238
- with console.status(f"正在卸载 {package_name}..."):
239
- result = subprocess.run(
240
- [sys.executable, "-m", "pip", "uninstall", "-y", package_name],
241
- check=True
242
- )
243
- if result.returncode == 0:
244
- console.print(Panel(
245
- f"包 {package_name} 卸载成功",
246
- title="成功",
247
- style="success"
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
- @staticmethod
260
- def upgrade_all() -> bool:
275
+ def upgrade_all(self) -> bool:
261
276
  """
262
277
  升级所有已安装的ErisPulse包
263
278
 
264
- :return: bool 升级是否成功
279
+ :return: 升级是否成功
265
280
  """
266
- try:
267
- installed = PyPIManager.get_installed_packages()
268
- all_packages = set()
269
-
270
- for pkg_type in ["modules", "adapters"]:
271
- for pkg_info in installed[pkg_type].values():
272
- all_packages.add(pkg_info["package"])
273
-
274
- if not all_packages:
275
- console.print(Panel(
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
- if not Confirm.ask("确认升级所有包吗?", default=False):
290
- return False
291
-
292
- for pkg in all_packages:
293
- PyPIManager.install_package(pkg, upgrade=True)
294
-
295
- return True
296
- except Exception as e:
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"升级包失败: {e}",
299
- title="错误",
300
- style="error"
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, *args, **kwargs):
311
- super().__init__(*args, **kwargs)
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
- console.print(f"[bold green]启动进程:[/] {self.script_path}")
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
- console.print(f"\n[cyan][热重载] 文件发生变动: {event.src_path}[/]")
342
- self.start_process()
343
- elif event.src_path.endswith("config.toml"):
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 start_reloader(script_path, reload_mode=False):
348
- """
349
- 启动热重载监控
350
-
351
- :param script_path: str 要监控的脚本路径
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
- def main():
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
- parser = argparse.ArgumentParser(
421
- prog="epsdk",
422
- formatter_class=argparse.RawTextHelpFormatter,
423
- description=f"ErisPulse SDK 命令行工具 {get_erispulse_version()}"
424
- )
425
- parser._positionals.title = "基本"
426
- parser._optionals.title = "可选"
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
- parser.add_argument("--version", action="store_true", help="显示版本信息")
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
- subparsers = parser.add_subparsers(
431
- dest='command',
432
- metavar="<命令>",
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
- for entry in cli_entries:
444
- try:
445
- cli_func = entry.load()
446
- if callable(cli_func):
447
- # 直接调用函数并传入subparsers和console
448
- cli_func(subparsers, console)
449
- else:
450
- console.print(f"[yellow]模块 {entry.name} 的入口点不是可调用对象[/]")
451
- except Exception as e:
452
- console.print(f"[red]加载第三方命令 {entry.name} 失败: {e}[/]")
453
- import traceback
454
- console.print(traceback.format_exc())
455
- except Exception as e:
456
- console.print(f"[yellow]加载第三方CLI命令失败: {e}[/]", style="warning")
457
-
458
- # 安装命令
459
- install_parser = subparsers.add_parser('install', help='安装模块/适配器包')
460
- install_parser.add_argument('package', type=str, help='要安装的包名')
461
- install_parser.add_argument('--upgrade', '-U', action='store_true', help='升级已安装的包')
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
- uninstall_parser = subparsers.add_parser('uninstall', help='卸载模块/适配器包')
465
- uninstall_parser.add_argument('package', type=str, help='要卸载的包名')
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
- enable_parser = subparsers.add_parser('enable', help='启用模块')
469
- enable_parser.add_argument('module', type=str, help='要启用的模块名')
470
-
471
- # 禁用命令
472
- disable_parser = subparsers.add_parser('disable', help='禁用模块')
473
- disable_parser.add_argument('module', type=str, help='要禁用的模块名')
474
-
475
- # 列表命令
476
- list_parser = subparsers.add_parser('list', help='列出已安装的模块/适配器')
477
- list_parser.add_argument('--type', '-t', choices=['modules', 'adapters', 'cli', 'all'], default='all',
478
- help='列出类型 (modules: 仅模块, adapters: 仅适配器, cli: 仅CLI扩展, all: 全部)')
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
- list_remote_parser = subparsers.add_parser('list-remote', help='列出远程可用的模块和适配器')
482
- list_remote_parser.add_argument('--type', '-t', choices=['modules', 'adapters', 'cli', 'all'], default='all',
483
- help='列出类型 (modules: 仅模块, adapters: 仅适配器, cli: 仅CLI扩展, all: 全部)')
484
- # 升级命令
485
- upgrade_parser = subparsers.add_parser('upgrade', help='升级所有模块/适配器')
486
- upgrade_parser.add_argument('--force', '-f', action='store_true', help='跳过确认直接升级')
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
- run_parser = subparsers.add_parser('run', help='运行指定主程序(默认为main.py)')
490
- run_parser.add_argument('script', type=str, nargs='?', help='要运行的主程序路径(可选,默认为main.py)')
491
- run_parser.add_argument('--reload', action='store_true', help='启用热重载模式')
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
- args = parser.parse_args()
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
- if args.version:
496
- console.print(f"[green]ErisPulse {get_erispulse_version()}[/]")
497
- return
816
+ def run(self):
817
+ """运行CLI应用程序"""
818
+ args = self.parser.parse_args()
498
819
 
499
- if not args.command:
500
- parser.print_help()
501
-
502
- try:
503
- if hasattr(args, 'func'):
504
- args.func(args)
505
- if args.command == "install":
506
- import asyncio
507
- # 首先检查是否是远程模块/适配器的简称
508
- remote_packages = asyncio.run(PyPIManager.get_remote_packages())
509
- full_package_name = None
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
- elif args.command == "uninstall":
531
- PyPIManager.uninstall_package(args.package)
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
- # 获取已安装的CLI扩展
571
- cli_extensions = {}
572
- try:
573
- entry_points = importlib.metadata.entry_points()
574
- if hasattr(entry_points, 'select'):
575
- cli_entries = entry_points.select(group='erispulse.cli')
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
- cli_entries = entry_points.get('erispulse.cli', [])
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
- for name, info in cli_extensions.items():
621
- table.add_row(name, info["package"], info["version"], info["summary"])
622
- console.print(table)
846
+ elif args.command == "uninstall":
847
+ self.package_manager.uninstall_package(args.package)
623
848
 
624
- if not installed["modules"] and not installed["adapters"] and not cli_extensions:
625
- console.print(Panel(
626
- "没有安装任何ErisPulse模块、适配器或CLI扩展",
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
- elif args.command == "upgrade":
632
- if args.force or Confirm.ask("确定要升级所有ErisPulse模块和适配器吗?", default=False):
633
- PyPIManager.upgrade_all()
634
-
635
- elif args.command == "run":
636
- if not hasattr(args, 'script') or not args.script:
637
- if not os.path.exists("config.toml") or not os.path.isfile("main.py"):
638
- from ErisPulse import sdk
639
- sdk.init()
640
- args.script = "main.py"
641
- console.print(Panel(
642
- "未指定主程序,运行入口点为 [bold]main.py[/]",
643
- title="提示",
644
- style="info"
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
- console.print(table)
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
- if args.type in ["adapters", "all"] and remote_packages["adapters"]:
665
- table = Table(title="远程适配器", box=SIMPLE)
666
- table.add_column("适配器名", style="yellow")
667
- table.add_column("包名", style="blue")
668
- table.add_column("版本")
669
- table.add_column("描述")
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
- for name, info in remote_packages["adapters"].items():
672
- table.add_row(name, info["package"], info["version"], info["description"])
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
- console.print(table)
900
+ reload_mode = args.reload and not args.no_reload
901
+ self._setup_watchdog(script, reload_mode)
675
902
 
676
- # 展示远程CLI扩展
677
- if "cli_extensions" in remote_packages and args.type in ["cli", "all"] and remote_packages["cli_extensions"]:
678
- table = Table(title="远程CLI扩展", box=SIMPLE)
679
- table.add_column("命令名", style="magenta")
680
- table.add_column("包名", style="blue")
681
- table.add_column("版本")
682
- table.add_column("描述")
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
- except Exception as e:
700
- console.print(Panel(
701
- f"获取远程列表失败: {e}",
702
- title="错误",
703
- style="error"
704
- ))
705
- elif args.command == "init":
706
- from ErisPulse import sdk
707
- sdk.init()
708
- console.print(Panel(
709
- "初始化完成",
710
- title="成功",
711
- style="success"
712
- ))
713
- except KeyboardInterrupt as e:
714
- pass
715
- except Exception as e:
716
- console.print(Panel(
717
- f"执行命令时出错: {e}",
718
- title="错误",
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()