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.
- {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/METADATA +1 -1
- {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/RECORD +50 -48
- autocoder/agent/planner.py +4 -4
- autocoder/auto_coder.py +26 -21
- autocoder/auto_coder_server.py +7 -7
- autocoder/chat_auto_coder.py +150 -49
- autocoder/commands/auto_command.py +81 -4
- autocoder/commands/tools.py +48 -50
- autocoder/common/__init__.py +0 -1
- autocoder/common/auto_coder_lang.py +37 -3
- autocoder/common/code_auto_generate.py +3 -3
- autocoder/common/code_auto_generate_diff.py +3 -6
- autocoder/common/code_auto_generate_editblock.py +3 -3
- autocoder/common/code_auto_generate_strict_diff.py +3 -3
- autocoder/common/code_auto_merge_diff.py +2 -2
- autocoder/common/code_auto_merge_editblock.py +1 -1
- autocoder/common/code_auto_merge_strict_diff.py +3 -3
- autocoder/common/command_completer.py +3 -0
- autocoder/common/command_generator.py +24 -8
- autocoder/common/command_templates.py +2 -2
- autocoder/common/conf_import_export.py +105 -0
- autocoder/common/conf_validator.py +1 -1
- autocoder/common/files.py +41 -2
- autocoder/common/image_to_page.py +11 -11
- autocoder/common/index_import_export.py +38 -18
- autocoder/common/mcp_hub.py +3 -3
- autocoder/common/mcp_server.py +2 -2
- autocoder/common/shells.py +254 -13
- autocoder/common/stats_panel.py +126 -0
- autocoder/dispacher/actions/action.py +6 -18
- autocoder/dispacher/actions/copilot.py +2 -2
- autocoder/dispacher/actions/plugins/action_regex_project.py +1 -3
- autocoder/dispacher/actions/plugins/action_translate.py +1 -1
- autocoder/index/index.py +5 -5
- autocoder/models.py +2 -2
- autocoder/pyproject/__init__.py +5 -5
- autocoder/rag/cache/byzer_storage_cache.py +4 -4
- autocoder/rag/cache/file_monitor_cache.py +2 -2
- autocoder/rag/cache/simple_cache.py +4 -4
- autocoder/rag/long_context_rag.py +2 -2
- autocoder/regexproject/__init__.py +3 -2
- autocoder/suffixproject/__init__.py +3 -2
- autocoder/tsproject/__init__.py +3 -2
- autocoder/utils/conversation_store.py +1 -1
- autocoder/utils/operate_config_api.py +3 -3
- autocoder/version.py +1 -1
- {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.264.dist-info → auto_coder-0.1.265.dist-info}/entry_points.txt +0 -0
- {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(
|
|
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(
|
|
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
|
-
|
|
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
|
autocoder/common/mcp_hub.py
CHANGED
|
@@ -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()
|
autocoder/common/mcp_server.py
CHANGED
|
@@ -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 []
|
autocoder/common/shells.py
CHANGED
|
@@ -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
|
-
#
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
388
|
+
# 首先尝试 UTF-8
|
|
389
|
+
return byte_stream.decode('utf-8').strip()
|
|
161
390
|
except UnicodeDecodeError:
|
|
162
|
-
|
|
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
|