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.
Files changed (64) hide show
  1. fr_cli/README.md +148 -0
  2. fr_cli/WEAPON.MD +186 -0
  3. fr_cli/__init__.py +4 -0
  4. fr_cli/addon/plugin.py +69 -0
  5. fr_cli/agent/__init__.py +9 -0
  6. fr_cli/agent/builtins/__init__.py +4 -0
  7. fr_cli/agent/builtins/_utils.py +48 -0
  8. fr_cli/agent/builtins/db.py +269 -0
  9. fr_cli/agent/builtins/local.py +105 -0
  10. fr_cli/agent/builtins/rag.py +652 -0
  11. fr_cli/agent/builtins/rag_watcher_daemon.py +156 -0
  12. fr_cli/agent/builtins/remote.py +214 -0
  13. fr_cli/agent/builtins/spider.py +247 -0
  14. fr_cli/agent/client.py +164 -0
  15. fr_cli/agent/executor.py +86 -0
  16. fr_cli/agent/generator.py +104 -0
  17. fr_cli/agent/manager.py +193 -0
  18. fr_cli/agent/master.py +604 -0
  19. fr_cli/agent/master_prompt.py +118 -0
  20. fr_cli/agent/remote.py +70 -0
  21. fr_cli/agent/server.py +279 -0
  22. fr_cli/agent/workflow.py +164 -0
  23. fr_cli/breakthrough/update.py +154 -0
  24. fr_cli/command/__init__.py +4 -0
  25. fr_cli/command/executor.py +276 -0
  26. fr_cli/command/registry.py +1034 -0
  27. fr_cli/command/security.py +30 -0
  28. fr_cli/conf/config.py +126 -0
  29. fr_cli/conf/wizard.py +172 -0
  30. fr_cli/core/chat.py +280 -0
  31. fr_cli/core/core.py +111 -0
  32. fr_cli/core/intent.py +129 -0
  33. fr_cli/core/recommender.py +71 -0
  34. fr_cli/core/stream.py +83 -0
  35. fr_cli/core/sysmon.py +117 -0
  36. fr_cli/core/thinking.py +215 -0
  37. fr_cli/gatekeeper/__init__.py +7 -0
  38. fr_cli/gatekeeper/daemon.py +216 -0
  39. fr_cli/gatekeeper/manager.py +218 -0
  40. fr_cli/lang/i18n.py +827 -0
  41. fr_cli/main.py +329 -0
  42. fr_cli/memory/context.py +119 -0
  43. fr_cli/memory/history.py +96 -0
  44. fr_cli/memory/session.py +134 -0
  45. fr_cli/repl/__init__.py +0 -0
  46. fr_cli/repl/commands.py +1098 -0
  47. fr_cli/security/security.py +46 -0
  48. fr_cli/ui/ui.py +116 -0
  49. fr_cli/weapon/cron.py +217 -0
  50. fr_cli/weapon/dataframe.py +97 -0
  51. fr_cli/weapon/disk.py +141 -0
  52. fr_cli/weapon/fs.py +206 -0
  53. fr_cli/weapon/launcher.py +249 -0
  54. fr_cli/weapon/loader.py +98 -0
  55. fr_cli/weapon/mail.py +227 -0
  56. fr_cli/weapon/mcp.py +204 -0
  57. fr_cli/weapon/vision.py +74 -0
  58. fr_cli/weapon/web.py +88 -0
  59. fr_cli-2.1.0.dist-info/METADATA +227 -0
  60. fr_cli-2.1.0.dist-info/RECORD +64 -0
  61. fr_cli-2.1.0.dist-info/WHEEL +5 -0
  62. fr_cli-2.1.0.dist-info/entry_points.txt +2 -0
  63. fr_cli-2.1.0.dist-info/licenses/LICENSE +21 -0
  64. 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}")