fr-cli 2.1.0__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.
- fr_cli/README.md +148 -0
- fr_cli/WEAPON.MD +186 -0
- fr_cli/__init__.py +4 -0
- fr_cli/addon/plugin.py +69 -0
- fr_cli/agent/__init__.py +9 -0
- fr_cli/agent/builtins/__init__.py +4 -0
- fr_cli/agent/builtins/_utils.py +48 -0
- fr_cli/agent/builtins/db.py +269 -0
- fr_cli/agent/builtins/local.py +105 -0
- fr_cli/agent/builtins/rag.py +652 -0
- fr_cli/agent/builtins/rag_watcher_daemon.py +156 -0
- fr_cli/agent/builtins/remote.py +214 -0
- fr_cli/agent/builtins/spider.py +247 -0
- fr_cli/agent/client.py +164 -0
- fr_cli/agent/executor.py +86 -0
- fr_cli/agent/generator.py +104 -0
- fr_cli/agent/manager.py +193 -0
- fr_cli/agent/master.py +604 -0
- fr_cli/agent/master_prompt.py +118 -0
- fr_cli/agent/remote.py +70 -0
- fr_cli/agent/server.py +279 -0
- fr_cli/agent/workflow.py +164 -0
- fr_cli/breakthrough/update.py +154 -0
- fr_cli/command/__init__.py +4 -0
- fr_cli/command/executor.py +276 -0
- fr_cli/command/registry.py +1034 -0
- fr_cli/command/security.py +30 -0
- fr_cli/conf/config.py +126 -0
- fr_cli/conf/wizard.py +172 -0
- fr_cli/core/chat.py +280 -0
- fr_cli/core/core.py +111 -0
- fr_cli/core/intent.py +129 -0
- fr_cli/core/recommender.py +71 -0
- fr_cli/core/stream.py +83 -0
- fr_cli/core/sysmon.py +117 -0
- fr_cli/core/thinking.py +215 -0
- fr_cli/gatekeeper/__init__.py +7 -0
- fr_cli/gatekeeper/daemon.py +216 -0
- fr_cli/gatekeeper/manager.py +218 -0
- fr_cli/lang/i18n.py +827 -0
- fr_cli/main.py +329 -0
- fr_cli/memory/context.py +119 -0
- fr_cli/memory/history.py +96 -0
- fr_cli/memory/session.py +134 -0
- fr_cli/repl/__init__.py +0 -0
- fr_cli/repl/commands.py +1098 -0
- fr_cli/security/security.py +46 -0
- fr_cli/ui/ui.py +116 -0
- fr_cli/weapon/cron.py +217 -0
- fr_cli/weapon/dataframe.py +97 -0
- fr_cli/weapon/disk.py +141 -0
- fr_cli/weapon/fs.py +206 -0
- fr_cli/weapon/launcher.py +249 -0
- fr_cli/weapon/loader.py +98 -0
- fr_cli/weapon/mail.py +227 -0
- fr_cli/weapon/mcp.py +204 -0
- fr_cli/weapon/vision.py +74 -0
- fr_cli/weapon/web.py +88 -0
- fr_cli-2.1.0.dist-info/METADATA +227 -0
- fr_cli-2.1.0.dist-info/RECORD +64 -0
- fr_cli-2.1.0.dist-info/WHEEL +5 -0
- fr_cli-2.1.0.dist-info/entry_points.txt +2 -0
- fr_cli-2.1.0.dist-info/licenses/LICENSE +21 -0
- fr_cli-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@db 内置 Agent —— 数据库智能助手
|
|
3
|
+
支持 MySQL / PostgreSQL / SQL Server / Oracle 的 Schema 分析和 SQL 生成。
|
|
4
|
+
"""
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
DB_CFG_PATH = Path.home() / ".fr_cli_databases.json"
|
|
8
|
+
|
|
9
|
+
DB_SYS_PROMPT = """你是一个数据库专家。请根据以下数据库 Schema 信息和用户需求,生成最合适的 SQL 语句。
|
|
10
|
+
|
|
11
|
+
规则:
|
|
12
|
+
1. 只输出 SQL 语句本身,不要任何解释、不要 markdown 代码块
|
|
13
|
+
2. 如果涉及 DELETE / DROP / TRUNCATE 等危险操作,输出 COMMENT: 警告
|
|
14
|
+
3. 优先使用标准 SQL,必要时针对特定数据库方言优化
|
|
15
|
+
4. 如果需求不明确,输出 COMMENT: 提示需要更多信息
|
|
16
|
+
5. 对于查询类需求,尽量给出列名明确的 SELECT 语句
|
|
17
|
+
|
|
18
|
+
数据库类型: {db_type}
|
|
19
|
+
数据库名: {database}
|
|
20
|
+
|
|
21
|
+
Schema 信息:
|
|
22
|
+
{schema_info}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_dbs():
|
|
27
|
+
from fr_cli.agent.builtins._utils import load_json_config
|
|
28
|
+
return load_json_config(DB_CFG_PATH)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _save_dbs(dbs):
|
|
32
|
+
from fr_cli.agent.builtins._utils import save_json_config
|
|
33
|
+
save_json_config(DB_CFG_PATH, dbs)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _connect(db_cfg):
|
|
37
|
+
"""根据配置建立数据库连接"""
|
|
38
|
+
db_type = db_cfg["type"]
|
|
39
|
+
host = db_cfg["host"]
|
|
40
|
+
port = db_cfg.get("port")
|
|
41
|
+
user = db_cfg["user"]
|
|
42
|
+
password = db_cfg["password"]
|
|
43
|
+
database = db_cfg.get("database", "")
|
|
44
|
+
|
|
45
|
+
if db_type == "mysql":
|
|
46
|
+
import pymysql
|
|
47
|
+
conn = pymysql.connect(
|
|
48
|
+
host=host, port=int(port) if port else 3306,
|
|
49
|
+
user=user, password=password, database=database,
|
|
50
|
+
charset="utf8mb4", cursorclass=pymysql.cursors.DictCursor
|
|
51
|
+
)
|
|
52
|
+
return conn
|
|
53
|
+
elif db_type == "postgresql":
|
|
54
|
+
import psycopg2
|
|
55
|
+
conn = psycopg2.connect(
|
|
56
|
+
host=host, port=port or "5432",
|
|
57
|
+
user=user, password=password, dbname=database
|
|
58
|
+
)
|
|
59
|
+
return conn
|
|
60
|
+
elif db_type == "sqlserver":
|
|
61
|
+
import pyodbc
|
|
62
|
+
port_str = f",{port}" if port else ""
|
|
63
|
+
conn_str = f"DRIVER={{ODBC Driver 17 for SQL Server}};SERVER={host}{port_str};DATABASE={database};UID={user};PWD={password}"
|
|
64
|
+
conn = pyodbc.connect(conn_str)
|
|
65
|
+
return conn
|
|
66
|
+
elif db_type == "oracle":
|
|
67
|
+
import oracledb
|
|
68
|
+
dsn = oracledb.makedsn(host, int(port) if port else 1521, service_name=database)
|
|
69
|
+
conn = oracledb.connect(user=user, password=password, dsn=dsn)
|
|
70
|
+
return conn
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"不支持的数据库类型: {db_type}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _get_schema_info(conn, db_type):
|
|
76
|
+
"""获取数据库 Schema 信息(表、列、主键、外键)"""
|
|
77
|
+
info = []
|
|
78
|
+
cursor = conn.cursor()
|
|
79
|
+
|
|
80
|
+
if db_type == "mysql":
|
|
81
|
+
cursor.execute("SHOW TABLES")
|
|
82
|
+
tables = [list(row.values())[0] for row in cursor.fetchall()]
|
|
83
|
+
for table in tables:
|
|
84
|
+
info.append(f"\n表: {table}")
|
|
85
|
+
cursor.execute(f"DESCRIBE `{table}`")
|
|
86
|
+
for col in cursor.fetchall():
|
|
87
|
+
info.append(f" {col['Field']} {col['Type']} {'PK' if col['Key'] == 'PRI' else ''}")
|
|
88
|
+
elif db_type == "postgresql":
|
|
89
|
+
cursor.execute("""
|
|
90
|
+
SELECT table_name FROM information_schema.tables
|
|
91
|
+
WHERE table_schema = 'public' ORDER BY table_name
|
|
92
|
+
""")
|
|
93
|
+
tables = [r[0] for r in cursor.fetchall()]
|
|
94
|
+
for table in tables:
|
|
95
|
+
info.append(f"\n表: {table}")
|
|
96
|
+
cursor.execute("""
|
|
97
|
+
SELECT column_name, data_type, is_nullable
|
|
98
|
+
FROM information_schema.columns
|
|
99
|
+
WHERE table_name = %s ORDER BY ordinal_position
|
|
100
|
+
""", (table,))
|
|
101
|
+
for col in cursor.fetchall():
|
|
102
|
+
info.append(f" {col[0]} {col[1]} {'NULL' if col[2] == 'YES' else 'NOT NULL'}")
|
|
103
|
+
elif db_type == "sqlserver":
|
|
104
|
+
cursor.execute("SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'")
|
|
105
|
+
tables = [r[0] for r in cursor.fetchall()]
|
|
106
|
+
for table in tables:
|
|
107
|
+
info.append(f"\n表: {table}")
|
|
108
|
+
cursor.execute(f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{table}'")
|
|
109
|
+
for col in cursor.fetchall():
|
|
110
|
+
info.append(f" {col[0]} {col[1]}")
|
|
111
|
+
elif db_type == "oracle":
|
|
112
|
+
cursor.execute("SELECT table_name FROM user_tables ORDER BY table_name")
|
|
113
|
+
tables = [r[0] for r in cursor.fetchall()]
|
|
114
|
+
for table in tables:
|
|
115
|
+
info.append(f"\n表: {table}")
|
|
116
|
+
cursor.execute(f"SELECT column_name, data_type FROM user_tab_columns WHERE table_name = '{table}' ORDER BY column_id")
|
|
117
|
+
for col in cursor.fetchall():
|
|
118
|
+
info.append(f" {col[0]} {col[1]}")
|
|
119
|
+
|
|
120
|
+
return "\n".join(info)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _exec_sql(conn, sql, db_type):
|
|
124
|
+
"""执行 SQL 并返回结果"""
|
|
125
|
+
cursor = conn.cursor()
|
|
126
|
+
try:
|
|
127
|
+
# 限制只执行单条语句,防止注入
|
|
128
|
+
sql = sql.strip().rstrip(";")
|
|
129
|
+
cursor.execute(sql)
|
|
130
|
+
|
|
131
|
+
# 如果是 SELECT,返回结果集
|
|
132
|
+
if sql.lower().startswith("select") or sql.lower().startswith("show") or sql.lower().startswith("desc"):
|
|
133
|
+
rows = cursor.fetchall()
|
|
134
|
+
if db_type == "mysql":
|
|
135
|
+
return rows, None
|
|
136
|
+
else:
|
|
137
|
+
# 将 pyodbc/psycopg2 的行转为字典列表
|
|
138
|
+
cols = [desc[0] for desc in cursor.description] if cursor.description else []
|
|
139
|
+
return [{cols[i]: row[i] for i in range(len(cols))} for row in rows], None
|
|
140
|
+
else:
|
|
141
|
+
conn.commit()
|
|
142
|
+
return f"受影响行数: {cursor.rowcount}", None
|
|
143
|
+
except Exception as e:
|
|
144
|
+
return None, str(e)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def handle_db(user_input, state):
|
|
148
|
+
"""处理 @db 前缀的请求"""
|
|
149
|
+
from fr_cli.core.stream import stream_cnt
|
|
150
|
+
from fr_cli.ui.ui import CYAN, GREEN, RED, YELLOW, DIM, RESET
|
|
151
|
+
|
|
152
|
+
dbs = _load_dbs()
|
|
153
|
+
if not dbs:
|
|
154
|
+
print(f"{YELLOW}未配置数据库。正在启动配置向导...{RESET}")
|
|
155
|
+
_setup_wizard(state.lang)
|
|
156
|
+
dbs = _load_dbs()
|
|
157
|
+
if not dbs:
|
|
158
|
+
print(f"{RED}配置取消。{RESET}")
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
# 解析: @db [别名] 需求
|
|
162
|
+
text = user_input[len("@db"):].strip()
|
|
163
|
+
parts = text.split(None, 1)
|
|
164
|
+
|
|
165
|
+
if len(dbs) == 1:
|
|
166
|
+
alias = list(dbs.keys())[0]
|
|
167
|
+
query = text
|
|
168
|
+
else:
|
|
169
|
+
if len(parts) < 2:
|
|
170
|
+
print(f"{YELLOW}用法: @db <别名> <需求>{RESET}")
|
|
171
|
+
print(f"{DIM}已配置数据库: {', '.join(dbs.keys())}{RESET}")
|
|
172
|
+
return
|
|
173
|
+
alias = parts[0]
|
|
174
|
+
query = parts[1]
|
|
175
|
+
|
|
176
|
+
db_cfg = dbs.get(alias)
|
|
177
|
+
if not db_cfg:
|
|
178
|
+
print(f"{RED}未找到数据库 [{alias}]。已配置: {', '.join(dbs.keys())}{RESET}")
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
conn = _connect(db_cfg)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
print(f"{RED}数据库连接失败: {e}{RESET}")
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
print(f"{CYAN}📊 正在分析 {db_cfg['type'].upper()} [{alias}]({db_cfg.get('database','')}) 的 Schema...{RESET}")
|
|
189
|
+
schema = _get_schema_info(conn, db_cfg["type"])
|
|
190
|
+
|
|
191
|
+
prompt = DB_SYS_PROMPT.format(
|
|
192
|
+
db_type=db_cfg["type"].upper(),
|
|
193
|
+
database=db_cfg.get("database", ""),
|
|
194
|
+
schema_info=schema[:3000]
|
|
195
|
+
)
|
|
196
|
+
messages = [
|
|
197
|
+
{"role": "system", "content": prompt},
|
|
198
|
+
{"role": "user", "content": query},
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
print(f"{CYAN}🧙 正在生成 SQL...{RESET}")
|
|
202
|
+
sql_text, _, _ = stream_cnt(state.client, state.model_name, messages, state.lang, custom_prefix="", max_tokens=1024)
|
|
203
|
+
sql_text = sql_text.strip()
|
|
204
|
+
|
|
205
|
+
from fr_cli.agent.builtins._utils import strip_code_blocks
|
|
206
|
+
sql_text = strip_code_blocks(sql_text)
|
|
207
|
+
|
|
208
|
+
if sql_text.startswith("COMMENT:"):
|
|
209
|
+
print(f"{YELLOW}{sql_text}{RESET}")
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
print(f"\n{DIM}生成 SQL:{RESET}\n{CYAN}{sql_text}{RESET}")
|
|
213
|
+
from fr_cli.agent.builtins._utils import confirm_execute
|
|
214
|
+
if not confirm_execute():
|
|
215
|
+
print(f"{DIM}已取消。{RESET}")
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
result, err = _exec_sql(conn, sql_text, db_cfg["type"])
|
|
219
|
+
if err:
|
|
220
|
+
print(f"{RED}❌ 执行失败: {err}{RESET}")
|
|
221
|
+
else:
|
|
222
|
+
if isinstance(result, list):
|
|
223
|
+
print(f"\n{GREEN}返回 {len(result)} 行:{RESET}")
|
|
224
|
+
for i, row in enumerate(result[:20]):
|
|
225
|
+
print(f" {row}")
|
|
226
|
+
if len(result) > 20:
|
|
227
|
+
print(f" {DIM}... 还有 {len(result)-20} 行{RESET}")
|
|
228
|
+
else:
|
|
229
|
+
print(f"{GREEN}{result}{RESET}")
|
|
230
|
+
finally:
|
|
231
|
+
conn.close()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _setup_wizard(lang="zh"):
|
|
235
|
+
"""数据库配置向导"""
|
|
236
|
+
from fr_cli.ui.ui import CYAN, GREEN, YELLOW, DIM, RESET
|
|
237
|
+
|
|
238
|
+
print(f"{CYAN}═══ 数据库配置向导 ═══{RESET}")
|
|
239
|
+
alias = input(f"{DIM}别名 (如: mydb): {RESET}").strip()
|
|
240
|
+
if not alias:
|
|
241
|
+
print(f"{YELLOW}别名不能为空。{RESET}")
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
db_type = input(f"{DIM}数据库类型 (mysql/postgresql/sqlserver/oracle) [mysql]: {RESET}").strip() or "mysql"
|
|
245
|
+
if db_type not in ("mysql", "postgresql", "sqlserver", "oracle"):
|
|
246
|
+
print(f"{YELLOW}不支持的数据库类型。{RESET}")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
host = input(f"{DIM}主机地址 [localhost]: {RESET}").strip() or "localhost"
|
|
250
|
+
port_default = {"mysql": "3306", "postgresql": "5432", "sqlserver": "1433", "oracle": "1521"}[db_type]
|
|
251
|
+
port = input(f"{DIM}端口 [{port_default}]: {RESET}").strip() or port_default
|
|
252
|
+
user = input(f"{DIM}用户名: {RESET}").strip()
|
|
253
|
+
if not user:
|
|
254
|
+
print(f"{YELLOW}用户名不能为空。{RESET}")
|
|
255
|
+
return
|
|
256
|
+
password = input(f"{DIM}密码: {RESET}").strip()
|
|
257
|
+
database = input(f"{DIM}数据库名: {RESET}").strip()
|
|
258
|
+
|
|
259
|
+
dbs = _load_dbs()
|
|
260
|
+
dbs[alias] = {
|
|
261
|
+
"type": db_type,
|
|
262
|
+
"host": host,
|
|
263
|
+
"port": port,
|
|
264
|
+
"user": user,
|
|
265
|
+
"password": password,
|
|
266
|
+
"database": database,
|
|
267
|
+
}
|
|
268
|
+
_save_dbs(dbs)
|
|
269
|
+
print(f"{GREEN}✅ 数据库 [{alias}] ({db_type}) 已保存。{RESET}")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@local 内置 Agent —— 本地系统操作助手
|
|
3
|
+
将用户需求 + 当前 OS 信息提交大模型,生成并执行系统命令。
|
|
4
|
+
"""
|
|
5
|
+
import platform
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
LOCAL_SYS_PROMPT = """你是一个系统命令专家。请根据用户的操作系统类型和需求,生成最合适、最安全的系统命令。
|
|
10
|
+
|
|
11
|
+
规则:
|
|
12
|
+
1. 只输出命令本身,不要任何解释、不要 markdown 代码块、不要多余文字
|
|
13
|
+
2. 如果需求涉及危险操作(rm -rf、格式化磁盘等),输出 COMMENT: 开头的注释警告
|
|
14
|
+
3. 优先使用跨平台兼容的命令,如果无法兼容则针对当前 OS 生成
|
|
15
|
+
4. 如果需要多条命令,用 && 或 ; 连接成一行(Windows 下用 ;)
|
|
16
|
+
5. 如果用户只是想查看信息,用安全的只读命令
|
|
17
|
+
|
|
18
|
+
当前操作系统: {os_name}
|
|
19
|
+
当前目录: {cwd}
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
WINDOWS_PS_HINT = """
|
|
23
|
+
注意:当前系统为 Windows,请使用 PowerShell 命令,不要使用 Linux/bash 命令。
|
|
24
|
+
常用映射参考:
|
|
25
|
+
- ls / dir → Get-ChildItem
|
|
26
|
+
- cat → Get-Content
|
|
27
|
+
- rm / rmdir → Remove-Item -Recurse -Force
|
|
28
|
+
- mkdir → New-Item -ItemType Directory
|
|
29
|
+
- pwd → Get-Location
|
|
30
|
+
- cp → Copy-Item
|
|
31
|
+
- mv → Move-Item
|
|
32
|
+
- grep → Select-String
|
|
33
|
+
- touch → New-Item
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def handle_local(user_input, state):
|
|
38
|
+
"""处理 @local 前缀的请求"""
|
|
39
|
+
from fr_cli.core.stream import stream_cnt
|
|
40
|
+
from fr_cli.ui.ui import CYAN, GREEN, RED, YELLOW, DIM, RESET
|
|
41
|
+
|
|
42
|
+
requirement = user_input[len("@local"):].strip()
|
|
43
|
+
if not requirement:
|
|
44
|
+
print(f"{RED}用法: @local <需求描述>{RESET}")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
os_name = platform.system()
|
|
48
|
+
cwd = state.vfs.cwd if state.vfs and state.vfs.cwd else "."
|
|
49
|
+
|
|
50
|
+
prompt = LOCAL_SYS_PROMPT.format(os_name=os_name, cwd=cwd)
|
|
51
|
+
if os_name == "Windows":
|
|
52
|
+
prompt += WINDOWS_PS_HINT
|
|
53
|
+
messages = [
|
|
54
|
+
{"role": "system", "content": prompt},
|
|
55
|
+
{"role": "user", "content": requirement},
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
print(f"{CYAN}🧙 正在分析本地操作...{RESET}")
|
|
59
|
+
cmd_text, _, _ = stream_cnt(state.client, state.model_name, messages, state.lang, custom_prefix="", max_tokens=1024)
|
|
60
|
+
cmd_text = cmd_text.strip()
|
|
61
|
+
|
|
62
|
+
# 清理可能的代码块
|
|
63
|
+
from fr_cli.agent.builtins._utils import strip_code_blocks
|
|
64
|
+
cmd_text = strip_code_blocks(cmd_text)
|
|
65
|
+
|
|
66
|
+
if not cmd_text:
|
|
67
|
+
print(f"{RED}未能生成有效命令。{RESET}")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if cmd_text.startswith("COMMENT:"):
|
|
71
|
+
print(f"{YELLOW}{cmd_text}{RESET}")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
print(f"\n{DIM}建议命令:{RESET}\n{CYAN}{cmd_text}{RESET}")
|
|
75
|
+
from fr_cli.agent.builtins._utils import confirm_execute
|
|
76
|
+
if not confirm_execute():
|
|
77
|
+
print(f"{DIM}已取消。{RESET}")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
print(f"{DIM}执行中...{RESET}")
|
|
82
|
+
if os_name == "Windows":
|
|
83
|
+
# Windows 下优先使用 PowerShell 执行,避免 cmd 无法识别 PowerShell 命令
|
|
84
|
+
ps_exe = shutil.which("pwsh") or shutil.which("powershell")
|
|
85
|
+
if ps_exe:
|
|
86
|
+
res = subprocess.run([ps_exe, "-Command", cmd_text], capture_output=True, text=True, timeout=30)
|
|
87
|
+
else:
|
|
88
|
+
res = subprocess.run(cmd_text, shell=True, capture_output=True, text=True, timeout=30)
|
|
89
|
+
else:
|
|
90
|
+
res = subprocess.run(cmd_text, shell=True, capture_output=True, text=True, timeout=30)
|
|
91
|
+
out = res.stdout + res.stderr
|
|
92
|
+
if out.strip():
|
|
93
|
+
print(f"\n{GREEN}{out.strip()[:3000]}{RESET}")
|
|
94
|
+
else:
|
|
95
|
+
print(f"{GREEN}✅ 命令执行完成(无输出){RESET}")
|
|
96
|
+
# 将结果追加到记忆
|
|
97
|
+
from fr_cli.agent.manager import save_memory, load_memory
|
|
98
|
+
mem = load_memory("__local__")
|
|
99
|
+
ts = __import__('datetime').datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
100
|
+
new_mem = f"\n[{ts}] 执行: {cmd_text}\n结果: {out.strip()[:500]}\n"
|
|
101
|
+
save_memory("__local__", (mem or "") + new_mem)
|
|
102
|
+
except subprocess.TimeoutExpired:
|
|
103
|
+
print(f"{RED}⏱️ 命令执行超时(30秒){RESET}")
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f"{RED}❌ 执行失败: {e}{RESET}")
|