auto-coder 0.1.264__py3-none-any.whl → 0.1.265__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.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

Files changed (50) hide show
  1. {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/RECORD +50 -48
  3. autocoder/agent/planner.py +4 -4
  4. autocoder/auto_coder.py +26 -21
  5. autocoder/auto_coder_server.py +7 -7
  6. autocoder/chat_auto_coder.py +150 -49
  7. autocoder/commands/auto_command.py +81 -4
  8. autocoder/commands/tools.py +48 -50
  9. autocoder/common/__init__.py +0 -1
  10. autocoder/common/auto_coder_lang.py +37 -3
  11. autocoder/common/code_auto_generate.py +3 -3
  12. autocoder/common/code_auto_generate_diff.py +3 -6
  13. autocoder/common/code_auto_generate_editblock.py +3 -3
  14. autocoder/common/code_auto_generate_strict_diff.py +3 -3
  15. autocoder/common/code_auto_merge_diff.py +2 -2
  16. autocoder/common/code_auto_merge_editblock.py +1 -1
  17. autocoder/common/code_auto_merge_strict_diff.py +3 -3
  18. autocoder/common/command_completer.py +3 -0
  19. autocoder/common/command_generator.py +24 -8
  20. autocoder/common/command_templates.py +2 -2
  21. autocoder/common/conf_import_export.py +105 -0
  22. autocoder/common/conf_validator.py +1 -1
  23. autocoder/common/files.py +41 -2
  24. autocoder/common/image_to_page.py +11 -11
  25. autocoder/common/index_import_export.py +38 -18
  26. autocoder/common/mcp_hub.py +3 -3
  27. autocoder/common/mcp_server.py +2 -2
  28. autocoder/common/shells.py +254 -13
  29. autocoder/common/stats_panel.py +126 -0
  30. autocoder/dispacher/actions/action.py +6 -18
  31. autocoder/dispacher/actions/copilot.py +2 -2
  32. autocoder/dispacher/actions/plugins/action_regex_project.py +1 -3
  33. autocoder/dispacher/actions/plugins/action_translate.py +1 -1
  34. autocoder/index/index.py +5 -5
  35. autocoder/models.py +2 -2
  36. autocoder/pyproject/__init__.py +5 -5
  37. autocoder/rag/cache/byzer_storage_cache.py +4 -4
  38. autocoder/rag/cache/file_monitor_cache.py +2 -2
  39. autocoder/rag/cache/simple_cache.py +4 -4
  40. autocoder/rag/long_context_rag.py +2 -2
  41. autocoder/regexproject/__init__.py +3 -2
  42. autocoder/suffixproject/__init__.py +3 -2
  43. autocoder/tsproject/__init__.py +3 -2
  44. autocoder/utils/conversation_store.py +1 -1
  45. autocoder/utils/operate_config_api.py +3 -3
  46. autocoder/version.py +1 -1
  47. {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/LICENSE +0 -0
  48. {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/WHEEL +0 -0
  49. {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/entry_points.txt +0 -0
  50. {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/top_level.txt +0 -0
@@ -122,7 +122,7 @@ class ImageToPageDirectly:
122
122
 
123
123
  counter = 1
124
124
  target_html_path = os.path.join(html_dir,f"{html_file_name}-{counter}.html")
125
- with open(target_html_path, "w") as f:
125
+ with open(target_html_path, "w",encoding="utf-8") as f:
126
126
  f.write(html)
127
127
 
128
128
  while counter < max_iter:
@@ -137,11 +137,11 @@ class ImageToPageDirectly:
137
137
 
138
138
  target_html_path = os.path.join(html_dir,f"{html_file_name}-{counter}.html")
139
139
  logger.info(f"generate html: {target_html_path}")
140
- with open(target_html_path, "w") as f:
140
+ with open(target_html_path, "w",encoding="utf-8") as f:
141
141
  f.write(html)
142
142
 
143
143
  logger.info(f"finally generate html: {html_path}")
144
- with open(html_path, "w") as f:
144
+ with open(html_path, "w",encoding="utf-8") as f:
145
145
  f.write(html)
146
146
 
147
147
 
@@ -248,7 +248,7 @@ class ImageToPage:
248
248
  file_path = block.path
249
249
  os.makedirs(os.path.dirname(file_path), exist_ok=True)
250
250
 
251
- with open(file_path, "w") as f:
251
+ with open(file_path, "w",encoding="utf-8") as f:
252
252
  logger.info(f"Upsert path: {file_path}")
253
253
  f.write(block.content)
254
254
  file_modified_num += 1
@@ -268,7 +268,7 @@ class ImageToPage:
268
268
  ## generate html by image description
269
269
  content_contains_html_prompt = self.generate_html.prompt(desc,html_path)
270
270
 
271
- with open(self.args.target_file, "w") as f:
271
+ with open(self.args.target_file, "w",encoding="utf-8") as f:
272
272
  f.write(content_contains_html_prompt)
273
273
 
274
274
  t = self.llm.chat_oai(conversations=[{
@@ -278,7 +278,7 @@ class ImageToPage:
278
278
 
279
279
  content_contains_html = t[0].output
280
280
 
281
- with open(self.args.target_file, "w") as f:
281
+ with open(self.args.target_file, "w",encoding="utf-8") as f:
282
282
  f.write(content_contains_html)
283
283
 
284
284
 
@@ -296,7 +296,7 @@ class ImageToPage:
296
296
 
297
297
  for i in range(max_iter):
298
298
  logger.info(f"iterate {i}")
299
- with open(html_path,"r") as f:
299
+ with open(html_path,"r",encoding="utf-8") as f:
300
300
  prev_html = f.read()
301
301
 
302
302
  gen_screenshots(url=html_path,image_dir=new_image_dir)
@@ -309,7 +309,7 @@ class ImageToPage:
309
309
  ## get new description prompt by comparing old and new image
310
310
  new_desc_prompt = self.get_optimize(self.score(origin_image,new_image))
311
311
 
312
- with open(self.args.target_file, "w") as f:
312
+ with open(self.args.target_file, "w",encoding="utf-8") as f:
313
313
  f.write(new_desc_prompt)
314
314
 
315
315
  t = self.llm.chat_oai(conversations=[{
@@ -319,7 +319,7 @@ class ImageToPage:
319
319
 
320
320
  new_desc = t[0].output
321
321
 
322
- with open(self.args.target_file, "w") as f:
322
+ with open(self.args.target_file, "w",encoding="utf-8") as f:
323
323
  f.write(new_desc)
324
324
 
325
325
  logger.info(f"score old/new image: {new_desc}")
@@ -327,7 +327,7 @@ class ImageToPage:
327
327
  ## generate new html by new description
328
328
  optimze_html_prompt = self.optimize_html.prompt(desc=new_desc,html=prev_html,html_path=html_path)
329
329
 
330
- with open(self.args.target_file, "w") as f:
330
+ with open(self.args.target_file, "w",encoding="utf-8") as f:
331
331
  f.write(optimze_html_prompt)
332
332
 
333
333
  t = self.llm.chat_oai(conversations=[{
@@ -336,7 +336,7 @@ class ImageToPage:
336
336
  }],llm_config={**extra_llm_config})
337
337
  new_code = t[0].output
338
338
 
339
- with open(self.args.target_file, "w") as f:
339
+ with open(self.args.target_file, "w",encoding="utf-8") as f:
340
340
  f.write(new_code)
341
341
 
342
342
  self.write_code(new_code,html_path)
@@ -3,6 +3,9 @@ import json
3
3
  import shutil
4
4
  from loguru import logger
5
5
  from autocoder.common.printer import Printer
6
+ from autocoder.common.result_manager import ResultManager
7
+
8
+ result_manager = ResultManager()
6
9
 
7
10
 
8
11
  def export_index(project_root: str, export_path: str) -> bool:
@@ -22,11 +25,11 @@ def export_index(project_root: str, export_path: str) -> bool:
22
25
  if not os.path.exists(index_path):
23
26
  printer.print_in_terminal("index_not_found", path=index_path)
24
27
  return False
25
-
28
+
26
29
  # Read and convert paths
27
- with open(index_path, "r") as f:
30
+ with open(index_path, "r",encoding="utf-8") as f:
28
31
  index_data = json.load(f)
29
-
32
+
30
33
  # Convert absolute paths to relative
31
34
  converted_data = {}
32
35
  for abs_path, data in index_data.items():
@@ -35,21 +38,29 @@ def export_index(project_root: str, export_path: str) -> bool:
35
38
  data["module_name"] = rel_path
36
39
  converted_data[rel_path] = data
37
40
  except ValueError:
38
- printer.print_in_terminal("index_convert_path_fail", path=abs_path)
41
+ printer.print_in_terminal(
42
+ "index_convert_path_fail", path=abs_path)
39
43
  converted_data[abs_path] = data
40
-
44
+
41
45
  # Write to export location
42
46
  export_file = os.path.join(export_path, "index.json")
43
47
  os.makedirs(export_path, exist_ok=True)
44
- with open(export_file, "w") as f:
48
+ with open(export_file, "w",encoding="utf-8") as f:
45
49
  json.dump(converted_data, f, indent=2)
46
-
50
+ printer.print_in_terminal("index_export_success", path=export_file)
51
+ result_manager.add_result(content=printer.get_message_from_key_with_format("index_export_success", path=export_file), meta={"action": "index_export", "input": {
52
+ "path": export_file
53
+ }})
47
54
  return True
48
-
55
+
49
56
  except Exception as e:
50
57
  printer.print_in_terminal("index_error", error=str(e))
58
+ result_manager.add_result(content=printer.get_message_from_key_with_format("index_error", error=str(e)), meta={"action": "index_export", "input": {
59
+ "path": export_file
60
+ }})
51
61
  return False
52
62
 
63
+
53
64
  def import_index(project_root: str, import_path: str) -> bool:
54
65
  printer = Printer()
55
66
  """
@@ -67,11 +78,11 @@ def import_index(project_root: str, import_path: str) -> bool:
67
78
  if not os.path.exists(import_file):
68
79
  printer.print_in_terminal("index_not_found", path=import_file)
69
80
  return False
70
-
81
+
71
82
  # Read and convert paths
72
- with open(import_file, "r") as f:
83
+ with open(import_file, "r",encoding="utf-8") as f:
73
84
  index_data = json.load(f)
74
-
85
+
75
86
  # Convert relative paths to absolute
76
87
  converted_data = {}
77
88
  for rel_path, data in index_data.items():
@@ -80,22 +91,31 @@ def import_index(project_root: str, import_path: str) -> bool:
80
91
  data["module_name"] = abs_path
81
92
  converted_data[abs_path] = data
82
93
  except Exception:
83
- printer.print_in_terminal("index_convert_path_fail", path=rel_path)
94
+ printer.print_in_terminal(
95
+ "index_convert_path_fail", path=rel_path)
84
96
  converted_data[rel_path] = data
85
-
97
+
86
98
  # Backup existing index
87
99
  index_path = os.path.join(project_root, ".auto-coder", "index.json")
88
100
  if os.path.exists(index_path):
89
101
  backup_path = index_path + ".bak"
90
102
  shutil.copy2(index_path, backup_path)
91
103
  printer.print_in_terminal("index_backup_success", path=backup_path)
92
-
104
+
93
105
  # Write new index
94
- with open(index_path, "w") as f:
106
+ with open(index_path, "w",encoding="utf-8") as f:
95
107
  json.dump(converted_data, f, indent=2)
96
-
97
- return True
98
108
 
109
+ printer.print_in_terminal("index_import_success", path=index_path)
110
+ result_manager.add_result(content=printer.get_message_from_key_with_format("index_import_success", path=index_path), meta={"action": "index_import", "input": {
111
+ "path": index_path
112
+ }})
113
+
114
+ return True
115
+
99
116
  except Exception as e:
100
117
  printer.print_in_terminal("index_error", error=str(e))
101
- return False
118
+ result_manager.add_result(content=printer.get_message_from_key_with_format("index_error", error=str(e)), meta={"action": "index_import", "input": {
119
+ "path": index_path
120
+ }})
121
+ return False
@@ -116,7 +116,7 @@ class McpHub:
116
116
  def _write_default_settings(self):
117
117
  """Write default MCP settings file"""
118
118
  default_settings = {"mcpServers": {}}
119
- with open(self.settings_path, "w") as f:
119
+ with open(self.settings_path, "w",encoding="utf-8") as f:
120
120
  json.dump(default_settings, f, indent=2)
121
121
 
122
122
  async def add_server_config(self, name: str, config:Dict[str,Any]) -> None:
@@ -129,7 +129,7 @@ class McpHub:
129
129
  try:
130
130
  settings = self._read_settings()
131
131
  settings["mcpServers"][name] = config
132
- with open(self.settings_path, "w") as f:
132
+ with open(self.settings_path, "w",encoding="utf-8") as f:
133
133
  json.dump(settings, f, indent=2, ensure_ascii=False)
134
134
  await self.initialize()
135
135
  logger.info(f"Added/updated MCP server config: {name}")
@@ -148,7 +148,7 @@ class McpHub:
148
148
  settings = self._read_settings()
149
149
  if name in settings["mcpServers"]:
150
150
  del settings["mcpServers"][name]
151
- with open(self.settings_path, "w") as f:
151
+ with open(self.settings_path, "w",encoding="utf-8") as f:
152
152
  json.dump(settings, f, indent=2, ensure_ascii=False)
153
153
  logger.info(f"Removed MCP server config: {name}")
154
154
  await self.initialize()
@@ -80,7 +80,7 @@ def get_mcp_external_servers() -> List[McpExternalServer]:
80
80
  if os.path.exists(cache_file):
81
81
  cache_time = os.path.getmtime(cache_file)
82
82
  if time.time() - cache_time < 3600: # 1 hour cache
83
- with open(cache_file, "r") as f:
83
+ with open(cache_file, "r",encoding="utf-8") as f:
84
84
  raw_data = json.load(f)
85
85
  return [McpExternalServer(**item) for item in raw_data]
86
86
 
@@ -91,7 +91,7 @@ def get_mcp_external_servers() -> List[McpExternalServer]:
91
91
  response = requests.get(url)
92
92
  if response.status_code == 200:
93
93
  raw_data = response.json()
94
- with open(cache_file, "w") as f:
94
+ with open(cache_file, "w",encoding="utf-8") as f:
95
95
  json.dump(raw_data, f)
96
96
  return [McpExternalServer(**item) for item in raw_data]
97
97
  return []
@@ -1,9 +1,10 @@
1
-
2
1
  import sys
3
2
  import os
4
3
  import locale
5
4
  import subprocess
6
5
  import platform
6
+ import tempfile
7
+ import uuid
7
8
  from rich.console import Console
8
9
  from rich.panel import Panel
9
10
  from rich.text import Text
@@ -21,18 +22,170 @@ def get_terminal_name() -> str:
21
22
  else:
22
23
  return _get_unix_terminal_name()
23
24
 
25
+ def is_running_in_powershell() -> bool:
26
+ """
27
+ 检查当前 Python 进程是否在 PowerShell 环境中运行
28
+ Returns:
29
+ bool: True 表示在 PowerShell 环境中,False 表示不在
30
+ """
31
+ try:
32
+ # 方法1: 检查特定的 PowerShell 环境变量
33
+ if any(key for key in os.environ if 'POWERSHELL' in key.upper()):
34
+ return True
35
+
36
+ # 方法2: 尝试执行 PowerShell 特定命令
37
+ try:
38
+ result = subprocess.run(
39
+ ['powershell', '-NoProfile', '-Command', '$PSVersionTable'],
40
+ capture_output=True,
41
+ timeout=1
42
+ )
43
+ if result.returncode == 0:
44
+ return True
45
+ except Exception:
46
+ pass
47
+
48
+ # 方法3: 检查父进程
49
+ try:
50
+ import psutil
51
+ current_process = psutil.Process()
52
+ parent = current_process.parent()
53
+ if parent:
54
+ parent_name = parent.name().lower()
55
+ if 'powershell' in parent_name or 'pwsh' in parent_name:
56
+ return True
57
+
58
+ # 递归检查父进程链
59
+ while parent and parent.pid != 1: # 1 是系统初始进程
60
+ if 'powershell' in parent.name().lower() or 'pwsh' in parent.name().lower():
61
+ return True
62
+ parent = parent.parent()
63
+ except Exception:
64
+ pass
65
+
66
+ # 方法4: 检查命令行参数
67
+ try:
68
+ import sys
69
+ if any('powershell' in arg.lower() for arg in sys.argv):
70
+ return True
71
+ except Exception:
72
+ pass
73
+
74
+ return False
75
+ except Exception:
76
+ return False
77
+
78
+ def is_running_in_cmd() -> bool:
79
+ """
80
+ 检查当前 Python 进程是否在 CMD 环境中运行
81
+ Returns:
82
+ bool: True 表示在 CMD 环境中,False 表示不在
83
+ """
84
+ # 如果在 PowerShell 中,直接返回 False
85
+ if is_running_in_powershell():
86
+ return False
87
+
88
+ try:
89
+ # 方法1: 检查特定的 CMD 环境变量
90
+ env = os.environ
91
+ # CMD 特有的环境变量
92
+ if 'PROMPT' in env and not any(key for key in env if 'POWERSHELL' in key.upper()):
93
+ return True
94
+
95
+ # 方法2: 检查 ComSpec 环境变量
96
+ comspec = env.get('ComSpec', '').lower()
97
+ if 'cmd.exe' in comspec:
98
+ return True
99
+
100
+ # 方法3: 检查父进程
101
+ try:
102
+ import psutil
103
+ current_process = psutil.Process()
104
+ parent = current_process.parent()
105
+ if parent:
106
+ parent_name = parent.name().lower()
107
+ if 'cmd.exe' in parent_name:
108
+ return True
109
+
110
+ # 递归检查父进程链
111
+ while parent and parent.pid != 1: # 1 是系统初始进程
112
+ if 'cmd.exe' in parent.name().lower():
113
+ return True
114
+ parent = parent.parent()
115
+ except Exception:
116
+ pass
117
+
118
+ return False
119
+ except Exception:
120
+ return False
121
+
24
122
  def _get_windows_terminal_name() -> str:
25
123
  """Windows 系统终端检测"""
26
- # 检查是否在 PowerShell
27
- if 'POWERSHELL_DISTRIBUTION_CHANNEL' in os.environ:
124
+ # 检查环境变量
125
+ env = os.environ
126
+
127
+ # 首先使用新方法检查是否在 PowerShell 环境中
128
+ if is_running_in_powershell():
129
+ # 进一步区分是否在 VSCode 的 PowerShell 终端
130
+ if 'VSCODE_GIT_IPC_HANDLE' in env:
131
+ return 'vscode-powershell'
28
132
  return 'powershell'
29
133
 
134
+ # 检查是否在 CMD 环境中
135
+ if is_running_in_cmd():
136
+ # 区分是否在 VSCode 的 CMD 终端
137
+ if 'VSCODE_GIT_IPC_HANDLE' in env:
138
+ return 'vscode-cmd'
139
+ return 'cmd'
140
+
30
141
  # 检查是否在 Git Bash
31
- if 'MINGW' in platform.system():
142
+ if ('MINGW' in platform.system() or
143
+ 'MSYSTEM' in env or
144
+ any('bash.exe' in path.lower() for path in env.get('PATH', '').split(os.pathsep))):
145
+ # 区分是否在 VSCode 的 Git Bash 终端
146
+ if 'VSCODE_GIT_IPC_HANDLE' in env:
147
+ return 'vscode-git-bash'
32
148
  return 'git-bash'
33
149
 
150
+ # 检查是否在 VSCode 的集成终端
151
+ if 'VSCODE_GIT_IPC_HANDLE' in env:
152
+ if 'WT_SESSION' in env: # Windows Terminal
153
+ return 'vscode-windows-terminal'
154
+ return 'vscode-terminal'
155
+
156
+ # 检查是否在 Windows Terminal
157
+ if 'WT_SESSION' in env:
158
+ return 'windows-terminal'
159
+
160
+ # 检查是否在 Cygwin
161
+ if 'CYGWIN' in platform.system():
162
+ return 'cygwin'
163
+
164
+ # 检查 TERM 环境变量
165
+ term = env.get('TERM', '').lower()
166
+ if term:
167
+ if 'xterm' in term:
168
+ return 'xterm'
169
+ elif 'cygwin' in term:
170
+ return 'cygwin'
171
+
172
+ # 检查进程名
173
+ try:
174
+ import psutil
175
+ parent = psutil.Process().parent()
176
+ if parent:
177
+ parent_name = parent.name().lower()
178
+ if 'powershell' in parent_name:
179
+ return 'powershell'
180
+ elif 'windowsterminal' in parent_name:
181
+ return 'windows-terminal'
182
+ elif 'cmd.exe' in parent_name:
183
+ return 'cmd'
184
+ except (ImportError, Exception):
185
+ pass
186
+
34
187
  # 默认返回 cmd.exe
35
- return 'cmd.exe'
188
+ return 'cmd'
36
189
 
37
190
  def _get_unix_terminal_name() -> str:
38
191
  """Linux/Mac 系统终端检测"""
@@ -138,28 +291,109 @@ def execute_shell_command(command: str):
138
291
 
139
292
  Args:
140
293
  command (str): The shell command to execute
141
- encoding (str, optional): Override default encoding. Defaults to None.
142
294
  """
143
295
  console = Console()
144
296
  result_manager = ResultManager()
297
+ temp_file = None
145
298
  try:
146
- # Get terminal encoding
299
+ # Get terminal encoding and name
147
300
  encoding = get_terminal_encoding()
301
+ terminal_name = get_terminal_name()
302
+
303
+ # Windows系统特殊处理
304
+ if sys.platform == 'win32':
305
+ # 设置控制台代码页为 UTF-8
306
+ os.system('chcp 65001 > nul')
307
+ # 强制使用 UTF-8 编码
308
+ encoding = 'utf-8'
309
+ # 设置环境变量
310
+ os.environ['PYTHONIOENCODING'] = 'utf-8'
311
+
312
+ # Create temp script file
313
+ if sys.platform == 'win32':
314
+ if is_running_in_powershell():
315
+ # Create temp PowerShell script with UTF-8 BOM
316
+ temp_file = tempfile.NamedTemporaryFile(
317
+ mode='wb',
318
+ suffix='.ps1',
319
+ delete=False
320
+ )
321
+ # 添加 UTF-8 BOM
322
+ temp_file.write(b'\xef\xbb\xbf')
323
+ # 设置输出编码
324
+ ps_command = f'$OutputEncoding = [Console]::OutputEncoding = [Text.Encoding]::UTF8\n{command}'
325
+ temp_file.write(ps_command.encode('utf-8'))
326
+ temp_file.close()
327
+ # Execute the temp script with PowerShell
328
+ command = f'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{temp_file.name}"'
329
+ elif is_running_in_cmd():
330
+ # Create temp batch script with UTF-8
331
+ temp_file = tempfile.NamedTemporaryFile(
332
+ mode='wb',
333
+ suffix='.cmd',
334
+ delete=False
335
+ )
336
+ # 添加 UTF-8 BOM
337
+ temp_file.write(b'\xef\xbb\xbf')
338
+ # 写入命令内容,确保UTF-8输出
339
+ content = f"""@echo off
340
+ chcp 65001 > nul
341
+ set PYTHONIOENCODING=utf-8
342
+ {command}
343
+ """
344
+ temp_file.write(content.encode('utf-8'))
345
+ temp_file.close()
346
+ # Execute the temp batch script
347
+ command = f'cmd.exe /c "{temp_file.name}"'
348
+ else:
349
+ # Create temp shell script for Unix-like systems
350
+ temp_file = tempfile.NamedTemporaryFile(
351
+ mode='w',
352
+ suffix='.sh',
353
+ encoding='utf-8',
354
+ delete=False
355
+ )
356
+ temp_file.write('#!/bin/bash\n' + command)
357
+ temp_file.close()
358
+ # Make the script executable
359
+ os.chmod(temp_file.name, 0o755)
360
+ command = temp_file.name
148
361
 
149
- # Start subprocess
362
+ # Start subprocess with UTF-8 encoding
363
+ startupinfo = None
364
+ if sys.platform == 'win32':
365
+ startupinfo = subprocess.STARTUPINFO()
366
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
367
+
368
+ # 创建子进程时设置环境变量
369
+ env = os.environ.copy()
370
+ env['PYTHONIOENCODING'] = 'utf-8'
371
+
150
372
  process = subprocess.Popen(
151
373
  command,
152
374
  stdout=subprocess.PIPE,
153
375
  stderr=subprocess.PIPE,
154
- shell=True
376
+ shell=True,
377
+ encoding='utf-8', # 直接指定 UTF-8 编码
378
+ errors='replace', # 处理无法解码的字符
379
+ env=env, # 传递修改后的环境变量
380
+ startupinfo=startupinfo
155
381
  )
156
382
 
157
- # Safe decoding helper
383
+ # Safe decoding helper (for binary output)
158
384
  def safe_decode(byte_stream, encoding):
385
+ if isinstance(byte_stream, str):
386
+ return byte_stream.strip()
159
387
  try:
160
- return byte_stream.decode(encoding).strip()
388
+ # 首先尝试 UTF-8
389
+ return byte_stream.decode('utf-8').strip()
161
390
  except UnicodeDecodeError:
162
- return byte_stream.decode(encoding, errors='replace').strip()
391
+ try:
392
+ # 如果失败,尝试 GBK
393
+ return byte_stream.decode('gbk').strip()
394
+ except UnicodeDecodeError:
395
+ # 最后使用替换模式
396
+ return byte_stream.decode(encoding, errors='replace').strip()
163
397
 
164
398
  output = []
165
399
  with Live(console=console, refresh_per_second=4) as live:
@@ -238,4 +472,11 @@ def execute_shell_command(command: str):
238
472
  })
239
473
  console.print(
240
474
  f"[bold red]Unexpected error:[/bold red] [yellow]{str(e)}[/yellow]"
241
- )
475
+ )
476
+ finally:
477
+ # Clean up temp file
478
+ if temp_file and os.path.exists(temp_file.name):
479
+ try:
480
+ os.unlink(temp_file.name)
481
+ except Exception:
482
+ pass