myagent-ai 1.22.2 → 1.23.1
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.
- package/agents/main_agent.py +42 -27
- package/core/tool_dispatcher.py +32 -1
- package/package.json +1 -1
- package/scripts/cli.py +371 -0
- package/start.js +38 -1
package/agents/main_agent.py
CHANGED
|
@@ -63,37 +63,52 @@ class MainAgent(BaseAgent):
|
|
|
63
63
|
</output>
|
|
64
64
|
|
|
65
65
|
注意事项:
|
|
66
|
-
1. toolstocal标签: 尽量一次性列出所有需执行工具调用的,多个"tool"
|
|
66
|
+
1. toolstocal标签: 尽量一次性列出所有需执行工具调用的,多个"tool"工具调用只要按顺序重复堆叠tool标签即可,解析器会按顺序执行工具调用,最终全部执行完后,会连同所有结果,回调大语言模型。如果某个工具执行超时了,也会回调大模型,让大模型分析为什么超时,改用其他工具。如非必要,不要一次仅调用一个工具。
|
|
67
67
|
2. 上下文中的记忆系统说明
|
|
68
68
|
- <automemory>: 系统自动根据你通过 <remember> 保存的记忆和当前用户输入,搜索出的 top10 相关记忆。这些是你过去主动记住的内容(包含时间信息),可供参考。
|
|
69
69
|
- <recall_memory>: 你在上一轮通过 <recall> 指定的记忆搜索结果。系统根据你提供的关键字和时间点搜索了 top5 相关记忆。
|
|
70
70
|
- 两种记忆互补:automemory 是自动匹配的,recall_memory 是你主动指定搜索的。如果 automemory 不足,使用 <recall> 请求更多。
|
|
71
|
-
3.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
|
|
71
|
+
3. 工具使用指南 — 只有两个工具: command 和 web_control
|
|
72
|
+
|
|
73
|
+
**command**(执行命令行,所有操作都通过它完成):
|
|
74
|
+
<tool><toolname>command</toolname><parms>{"command": "要执行的命令"}</parms><timeout>超时秒数</timeout></tool>
|
|
75
|
+
|
|
76
|
+
常用 CLI 命令 (通过 command 工具调用):
|
|
77
|
+
- OCR 文字识别: myagent-ai ocr 图片路径 [ch|en]
|
|
78
|
+
- 图片分析(VLM): myagent-ai analyze-image 图片路径 [分析提示词]
|
|
79
|
+
- 语音转文字: myagent-ai transcribe 音频路径 [zh|en|ja]
|
|
80
|
+
- 网络搜索: myagent-ai search 关键词
|
|
81
|
+
- 读取网页: myagent-ai read-url URL
|
|
82
|
+
- 发送文件给用户: myagent-ai send-file 文件路径 描述
|
|
83
|
+
- 读文件: cat 文件路径
|
|
84
|
+
- 写文件: echo "内容" > 文件路径 或 python3 -c "open('f','w').write('内容')"
|
|
85
|
+
- 执行代码: python3 script.py 或 python3 -c "代码"
|
|
86
|
+
- 文件列表: ls -la 目录
|
|
87
|
+
- 系统信息: uname -a / df -h / free -h
|
|
88
|
+
|
|
89
|
+
调用示例:
|
|
90
|
+
<tool><toolname>command</toolname><parms>{"command": "myagent-ai ocr /tmp/screenshot.png"}</parms><timeout>30</timeout></tool>
|
|
91
|
+
<tool><toolname>command</toolname><parms>{"command": "myagent-ai search 人工智能最新进展"}</parms><timeout>15</timeout></tool>
|
|
92
|
+
<tool><toolname>command</toolname><parms>{"command": "myagent-ai send-file /tmp/report.pdf 季度报告"}</parms><timeout>10</timeout></tool>
|
|
93
|
+
|
|
94
|
+
多个命令可用 && 连接一次执行:
|
|
95
|
+
<tool><toolname>command</toolname><parms>{"command": "myagent-ai search xxx && myagent-ai read-url https://..."}</parms><timeout>30</timeout></tool>
|
|
96
|
+
|
|
97
|
+
**web_control**(网页控制器,在聊天中打开可操作的浏览器面板):
|
|
98
|
+
<tool><toolname>web_control</toolname><parms>{"action": "open", "url": "https://example.com"}</parms><timeout>30</timeout></tool>
|
|
99
|
+
- 打开: {"action": "open", "url": "URL"}
|
|
100
|
+
- 导航: {"action": "navigate", "url": "URL", "session_id": "xxx"}
|
|
101
|
+
- 获取内容: {"action": "get_content", "what": "text|html|url|title|links|images|forms|inputs", "session_id": "xxx"}
|
|
102
|
+
- 点击: {"action": "click", "selector": "CSS选择器", "session_id": "xxx"}
|
|
103
|
+
- 填写: {"action": "fill", "selector": "CSS选择器", "value": "内容", "session_id": "xxx"}
|
|
104
|
+
- 滚动: {"action": "scroll", "direction": "up|down|top|bottom", "distance": 300, "session_id": "xxx"}
|
|
105
|
+
- 执行JS: {"action": "evaluate", "script": "JS代码", "session_id": "xxx"}
|
|
106
|
+
- 截图: {"action": "screenshot", "session_id": "xxx"}
|
|
107
|
+
- 等待: {"action": "wait", "time": 1000} 或 {"action": "wait", "selector": ".result", "timeout": 10}
|
|
108
|
+
- Cookie: {"action": "set_cookies", "cookies": [...], "session_id": "xxx"} 或 {"action": "get_cookies", "session_id": "xxx"}
|
|
109
|
+
- 关闭: {"action": "close", "session_id": "xxx"}
|
|
110
|
+
|
|
111
|
+
专业技能指令: 系统内置了丰富的专业技能指南(PDF/DOCX/XLSX/PPT 生成、图表绘制、前端开发等),通过 <get_knowledge> 请求相关技能指令。
|
|
97
112
|
4. 准备好内容后,最后,再检查输出格式,确保满足以下要求:
|
|
98
113
|
<output>
|
|
99
114
|
<mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
|
package/core/tool_dispatcher.py
CHANGED
|
@@ -169,7 +169,38 @@ class ToolDispatcher:
|
|
|
169
169
|
exec_result = await self.executor.execute(
|
|
170
170
|
language="shell", code=code_text, timeout=timeout,
|
|
171
171
|
)
|
|
172
|
-
|
|
172
|
+
result = exec_result.to_dict()
|
|
173
|
+
|
|
174
|
+
# [v1.22.0] 检测 __SEND_FILE__ 标记 — CLI send-file 命令输出此标记
|
|
175
|
+
# 格式: __SEND_FILE__绝对路径|描述__END__
|
|
176
|
+
output = result.get("output", "")
|
|
177
|
+
import re as _re
|
|
178
|
+
send_markers = _re.findall(r'__SEND_FILE__(.+?)\|(.+?)__END__', output)
|
|
179
|
+
if send_markers:
|
|
180
|
+
# 从输出中移除标记行
|
|
181
|
+
clean_output = _re.sub(r'__SEND_FILE__.+?__END__\n?', '', output).strip()
|
|
182
|
+
result["output"] = clean_output
|
|
183
|
+
# 执行 file_send
|
|
184
|
+
for send_path, send_desc in send_markers:
|
|
185
|
+
send_path = send_path.strip()
|
|
186
|
+
send_desc = send_desc.strip()
|
|
187
|
+
try:
|
|
188
|
+
p = _P(send_path)
|
|
189
|
+
if p.exists():
|
|
190
|
+
from skills.file_send import FileSendSkill
|
|
191
|
+
fskill = FileSendSkill()
|
|
192
|
+
fresult = await fskill.execute(
|
|
193
|
+
str(p), send_desc or f"文件: {p.name}",
|
|
194
|
+
)
|
|
195
|
+
if fresult.get("success"):
|
|
196
|
+
logger.info(f"[{task_id}] CLI 自动发送文件: {p.name}")
|
|
197
|
+
else:
|
|
198
|
+
result["output"] += f"\n[文件发送失败: {fresult.get('error', '')}]"
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.warning(f"[{task_id}] CLI 文件发送异常: {e}")
|
|
201
|
+
result["output"] += f"\n[文件发送异常: {e}]"
|
|
202
|
+
|
|
203
|
+
return result
|
|
173
204
|
|
|
174
205
|
async def _exec_recall_memory(self, params: Dict, task_id: str) -> Dict:
|
|
175
206
|
"""主动召回记忆"""
|
package/package.json
CHANGED
package/scripts/cli.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
scripts/cli.py - MyAgent CLI 工具集
|
|
4
|
+
====================================
|
|
5
|
+
轻量级命令行工具,LLM 通过 command 工具调用这些命令完成所有操作。
|
|
6
|
+
|
|
7
|
+
用法: myagent-ai <command> [args...]
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import base64
|
|
13
|
+
import json
|
|
14
|
+
import mimetypes
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# 确保可以导入 myagent 模块
|
|
20
|
+
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
21
|
+
_PROJECT_ROOT = os.path.dirname(_SCRIPT_DIR)
|
|
22
|
+
if _PROJECT_ROOT not in sys.path:
|
|
23
|
+
sys.path.insert(0, _PROJECT_ROOT)
|
|
24
|
+
|
|
25
|
+
_DATA_DIR = os.path.expanduser("~/.myagent/data")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _load_config() -> dict:
|
|
29
|
+
"""加载用户配置"""
|
|
30
|
+
config_path = os.path.join(_DATA_DIR, "config.json")
|
|
31
|
+
if os.path.exists(config_path):
|
|
32
|
+
with open(config_path, encoding="utf-8") as f:
|
|
33
|
+
return json.load(f)
|
|
34
|
+
return {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _err(msg: str) -> None:
|
|
38
|
+
print(f"错误: {msg}", file=sys.stderr)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# =============================================================================
|
|
42
|
+
# OCR 光学字符识别
|
|
43
|
+
# =============================================================================
|
|
44
|
+
def cmd_ocr(args: list[str]) -> int:
|
|
45
|
+
"""从图片中提取文字"""
|
|
46
|
+
image = args[0] if args else ""
|
|
47
|
+
lang = "ch"
|
|
48
|
+
for a in args[1:]:
|
|
49
|
+
if a in ("ch", "en", "japan", "korean", "ta", "te", "kn", "hi", "ar", "ur"):
|
|
50
|
+
lang = a
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
if not image:
|
|
54
|
+
_err("用法: myagent-ai ocr <图片路径> [ch|en|japan|korean|...]")
|
|
55
|
+
return 1
|
|
56
|
+
image = os.path.expanduser(image)
|
|
57
|
+
if not os.path.isfile(image):
|
|
58
|
+
_err(f"图片不存在: {image}")
|
|
59
|
+
return 1
|
|
60
|
+
|
|
61
|
+
ext = Path(image).suffix.lower().lstrip(".")
|
|
62
|
+
supported = ("png", "jpg", "jpeg", "bmp", "tiff", "tif", "webp", "gif")
|
|
63
|
+
if ext not in supported:
|
|
64
|
+
_err(f"不支持的图片格式: .{ext}")
|
|
65
|
+
return 1
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
from paddleocr import PaddleOCR
|
|
69
|
+
except ImportError:
|
|
70
|
+
_err("paddleocr 未安装。安装: pip install paddleocr paddlepaddle")
|
|
71
|
+
return 1
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
ocr = PaddleOCR(use_angle_cls=True, lang=lang, show_log=False)
|
|
75
|
+
result = ocr.ocr(image, cls=True)
|
|
76
|
+
if result and result[0]:
|
|
77
|
+
for line in result[0]:
|
|
78
|
+
text, (conf,) = line[1]
|
|
79
|
+
print(text)
|
|
80
|
+
else:
|
|
81
|
+
print("(未检测到文字)")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
_err(f"OCR 失败: {e}")
|
|
84
|
+
return 1
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# =============================================================================
|
|
89
|
+
# 图片内容分析 (VLM)
|
|
90
|
+
# =============================================================================
|
|
91
|
+
def cmd_analyze_image(args: list[str]) -> int:
|
|
92
|
+
"""使用视觉语言模型分析图片"""
|
|
93
|
+
image = args[0] if args else ""
|
|
94
|
+
# prompt 从 --prompt 后面取,或剩余参数拼接
|
|
95
|
+
prompt = ""
|
|
96
|
+
rest = args[1:]
|
|
97
|
+
if "--prompt" in rest:
|
|
98
|
+
idx = rest.index("--prompt")
|
|
99
|
+
prompt = " ".join(rest[idx + 1:]) if idx + 1 < len(rest) else ""
|
|
100
|
+
elif rest:
|
|
101
|
+
prompt = " ".join(rest)
|
|
102
|
+
if not prompt:
|
|
103
|
+
prompt = "请详细描述这张图片的内容,包括文字、布局、物体等信息。"
|
|
104
|
+
|
|
105
|
+
if not image:
|
|
106
|
+
_err("用法: myagent-ai analyze-image <图片路径> [--prompt 分析提示词]")
|
|
107
|
+
return 1
|
|
108
|
+
image = os.path.expanduser(image)
|
|
109
|
+
if not os.path.isfile(image):
|
|
110
|
+
_err(f"图片不存在: {image}")
|
|
111
|
+
return 1
|
|
112
|
+
|
|
113
|
+
# 检查图片大小
|
|
114
|
+
fsize = os.path.getsize(image)
|
|
115
|
+
if fsize > 20 * 1024 * 1024:
|
|
116
|
+
_err(f"图片过大 ({fsize / 1024 / 1024:.1f}MB),限制 20MB")
|
|
117
|
+
return 1
|
|
118
|
+
|
|
119
|
+
# 读取 LLM 配置
|
|
120
|
+
cfg = _load_config().get("llm", {})
|
|
121
|
+
api_key = cfg.get("api_key", "")
|
|
122
|
+
base_url = cfg.get("base_url", "")
|
|
123
|
+
model = cfg.get("model", "")
|
|
124
|
+
|
|
125
|
+
# 模型链支持: 检查是否有视觉模型
|
|
126
|
+
model_chain = cfg.get("model_chain", [])
|
|
127
|
+
vision_model = None
|
|
128
|
+
vision_base_url = base_url
|
|
129
|
+
for m in model_chain:
|
|
130
|
+
m_name = m.get("model", "")
|
|
131
|
+
m_modes = m.get("modes", [])
|
|
132
|
+
if "image" in m_modes:
|
|
133
|
+
vision_model = m_name
|
|
134
|
+
provider_cfg = m.get("provider", "")
|
|
135
|
+
if provider_cfg:
|
|
136
|
+
# 从 provider 配置中获取 base_url
|
|
137
|
+
provider_settings = cfg.get("providers", {}).get(provider_cfg, {})
|
|
138
|
+
vision_base_url = provider_settings.get("base_url", base_url)
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
if not vision_model and not api_key:
|
|
142
|
+
_err("未配置支持视觉的模型。请在配置中添加 image 模式的模型。")
|
|
143
|
+
return 1
|
|
144
|
+
|
|
145
|
+
use_model = vision_model or model
|
|
146
|
+
use_url = vision_base_url or base_url
|
|
147
|
+
|
|
148
|
+
if not use_url:
|
|
149
|
+
_err("未配置 LLM API 地址")
|
|
150
|
+
return 1
|
|
151
|
+
|
|
152
|
+
# 编码图片
|
|
153
|
+
with open(image, "rb") as f:
|
|
154
|
+
b64 = base64.b64encode(f.read()).decode()
|
|
155
|
+
mime = mimetypes.guess_type(image)[0] or "image/png"
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
from openai import OpenAI
|
|
159
|
+
client = OpenAI(api_key=api_key, base_url=use_url)
|
|
160
|
+
resp = client.chat.completions.create(
|
|
161
|
+
model=use_model,
|
|
162
|
+
messages=[{
|
|
163
|
+
"role": "user",
|
|
164
|
+
"content": [
|
|
165
|
+
{"type": "text", "text": prompt},
|
|
166
|
+
{"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}},
|
|
167
|
+
],
|
|
168
|
+
}],
|
|
169
|
+
max_tokens=4096,
|
|
170
|
+
temperature=0.1,
|
|
171
|
+
)
|
|
172
|
+
content = resp.choices[0].message.content
|
|
173
|
+
if content:
|
|
174
|
+
print(content)
|
|
175
|
+
else:
|
|
176
|
+
print("(模型未返回分析结果)")
|
|
177
|
+
except Exception as e:
|
|
178
|
+
_err(f"图片分析失败: {e}")
|
|
179
|
+
return 1
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# =============================================================================
|
|
184
|
+
# 语音转文字 (STT)
|
|
185
|
+
# =============================================================================
|
|
186
|
+
async def _do_transcribe(audio_path: str, language: str) -> dict:
|
|
187
|
+
from core.stt import transcribe
|
|
188
|
+
return await transcribe(audio_path=audio_path, language=language)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def cmd_transcribe(args: list[str]) -> int:
|
|
192
|
+
"""将音频文件转录为文本"""
|
|
193
|
+
audio = args[0] if args else ""
|
|
194
|
+
lang = "zh"
|
|
195
|
+
for a in args[1:]:
|
|
196
|
+
if a in ("zh", "en", "ja", "ko", "yue", "auto"):
|
|
197
|
+
lang = a
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
if not audio:
|
|
201
|
+
_err("用法: myagent-ai transcribe <音频路径> [zh|en|ja|ko|yue]")
|
|
202
|
+
return 1
|
|
203
|
+
audio = os.path.expanduser(audio)
|
|
204
|
+
if not os.path.isfile(audio):
|
|
205
|
+
_err(f"音频文件不存在: {audio}")
|
|
206
|
+
return 1
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
result = asyncio.run(_do_transcribe(audio, lang))
|
|
210
|
+
if result.get("success"):
|
|
211
|
+
print(result["output"])
|
|
212
|
+
return 0
|
|
213
|
+
else:
|
|
214
|
+
_err(result.get("error", "转录失败"))
|
|
215
|
+
return 1
|
|
216
|
+
except RuntimeError:
|
|
217
|
+
# 已有事件循环
|
|
218
|
+
loop = asyncio.new_event_loop()
|
|
219
|
+
asyncio.set_event_loop(loop)
|
|
220
|
+
try:
|
|
221
|
+
result = loop.run_until_complete(_do_transcribe(audio, lang))
|
|
222
|
+
if result.get("success"):
|
|
223
|
+
print(result["output"])
|
|
224
|
+
return 0
|
|
225
|
+
else:
|
|
226
|
+
_err(result.get("error", "转录失败"))
|
|
227
|
+
return 1
|
|
228
|
+
finally:
|
|
229
|
+
loop.close()
|
|
230
|
+
except Exception as e:
|
|
231
|
+
_err(f"语音转文字失败: {e}")
|
|
232
|
+
return 1
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# =============================================================================
|
|
236
|
+
# 网络搜索
|
|
237
|
+
# =============================================================================
|
|
238
|
+
def cmd_search(args: list[str]) -> int:
|
|
239
|
+
"""DuckDuckGo 网络搜索"""
|
|
240
|
+
query = " ".join(args)
|
|
241
|
+
if not query:
|
|
242
|
+
_err("用法: myagent-ai search <关键词>")
|
|
243
|
+
return 1
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
from duckduckgo_search import DDGS
|
|
247
|
+
with DDGS() as ddgs:
|
|
248
|
+
results = list(ddgs.text(query, max_results=5))
|
|
249
|
+
if results:
|
|
250
|
+
for i, r in enumerate(results, 1):
|
|
251
|
+
print(f"{i}. {r['title']}")
|
|
252
|
+
print(f" URL: {r['href']}")
|
|
253
|
+
print(f" {r['body']}")
|
|
254
|
+
print()
|
|
255
|
+
else:
|
|
256
|
+
print("(未找到搜索结果)")
|
|
257
|
+
except Exception as e:
|
|
258
|
+
_err(f"搜索失败: {e}")
|
|
259
|
+
return 1
|
|
260
|
+
return 0
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# =============================================================================
|
|
264
|
+
# 读取网页内容
|
|
265
|
+
# =============================================================================
|
|
266
|
+
def cmd_read_url(args: list[str]) -> int:
|
|
267
|
+
"""提取网页正文内容"""
|
|
268
|
+
url = args[0] if args else ""
|
|
269
|
+
if not url:
|
|
270
|
+
_err("用法: myagent-ai read-url <URL>")
|
|
271
|
+
return 1
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
import requests
|
|
275
|
+
from bs4 import BeautifulSoup
|
|
276
|
+
|
|
277
|
+
resp = requests.get(url, timeout=15, headers={
|
|
278
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
279
|
+
})
|
|
280
|
+
resp.raise_for_status()
|
|
281
|
+
soup = BeautifulSoup(resp.text, "html.parser")
|
|
282
|
+
# 移除无关标签
|
|
283
|
+
for tag in soup(["script", "style", "nav", "header", "footer", "aside"]):
|
|
284
|
+
tag.decompose()
|
|
285
|
+
# 提取标题
|
|
286
|
+
title = soup.title.string.strip() if soup.title else ""
|
|
287
|
+
if title:
|
|
288
|
+
print(f"标题: {title}\n")
|
|
289
|
+
text = soup.get_text(separator="\n", strip=True)
|
|
290
|
+
# 截断过长内容
|
|
291
|
+
if len(text) > 8000:
|
|
292
|
+
text = text[:8000] + "\n...(内容过长,已截断)"
|
|
293
|
+
print(text)
|
|
294
|
+
except Exception as e:
|
|
295
|
+
_err(f"读取网页失败: {e}")
|
|
296
|
+
return 1
|
|
297
|
+
return 0
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# =============================================================================
|
|
301
|
+
# 发送文件给用户
|
|
302
|
+
# =============================================================================
|
|
303
|
+
def cmd_send_file(args: list[str]) -> int:
|
|
304
|
+
"""发送文件给用户 (输出特殊标记,由 ToolDispatcher 解析)"""
|
|
305
|
+
fpath = args[0] if args else ""
|
|
306
|
+
desc = ""
|
|
307
|
+
if len(args) > 1:
|
|
308
|
+
# 支持两种格式:
|
|
309
|
+
# myagent-ai send-file path description
|
|
310
|
+
# myagent-ai send-file path --desc "description"
|
|
311
|
+
rest = args[1:]
|
|
312
|
+
if "--desc" in rest:
|
|
313
|
+
idx = rest.index("--desc")
|
|
314
|
+
desc = " ".join(rest[idx + 1:]) if idx + 1 < len(rest) else ""
|
|
315
|
+
else:
|
|
316
|
+
desc = " ".join(rest)
|
|
317
|
+
|
|
318
|
+
if not fpath:
|
|
319
|
+
_err("用法: myagent-ai send-file <文件路径> [描述]")
|
|
320
|
+
return 1
|
|
321
|
+
fpath = os.path.expanduser(fpath)
|
|
322
|
+
if not os.path.isfile(fpath):
|
|
323
|
+
_err(f"文件不存在: {fpath}")
|
|
324
|
+
return 1
|
|
325
|
+
|
|
326
|
+
# 输出特殊标记 (ToolDispatcher._exec_command 会检测并自动发送)
|
|
327
|
+
abs_path = os.path.abspath(fpath)
|
|
328
|
+
print(f"__SEND_FILE__{abs_path}|{desc}__END__")
|
|
329
|
+
return 0
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# =============================================================================
|
|
333
|
+
# 命令注册
|
|
334
|
+
# =============================================================================
|
|
335
|
+
COMMANDS: dict[str, tuple] = {
|
|
336
|
+
"ocr": (cmd_ocr, "OCR 光学字符识别", "<图片路径> [ch|en|japan|korean|...]"),
|
|
337
|
+
"analyze-image": (cmd_analyze_image, "图片内容分析 (VLM)", "<图片路径> [--prompt 分析提示词]"),
|
|
338
|
+
"transcribe": (cmd_transcribe, "语音转文字", "<音频路径> [zh|en|ja|ko|yue]"),
|
|
339
|
+
"search": (cmd_search, "网络搜索 (DuckDuckGo)", "<关键词>"),
|
|
340
|
+
"read-url": (cmd_read_url, "读取网页正文内容", "<URL>"),
|
|
341
|
+
"send-file": (cmd_send_file, "发送文件给用户", "<文件路径> [描述]"),
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def main():
|
|
346
|
+
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help", "help"):
|
|
347
|
+
print("MyAgent CLI - 命令行工具集\n")
|
|
348
|
+
print("用法: myagent-ai <command> [args...]\n")
|
|
349
|
+
print("可用命令:")
|
|
350
|
+
for name, (_, desc, usage) in COMMANDS.items():
|
|
351
|
+
print(f" myagent-ai {name} {usage}")
|
|
352
|
+
print(f" → {desc}")
|
|
353
|
+
print()
|
|
354
|
+
if len(sys.argv) < 2:
|
|
355
|
+
sys.exit(1)
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
cmd = sys.argv[1]
|
|
359
|
+
if cmd not in COMMANDS:
|
|
360
|
+
_err(f"未知命令: {cmd}")
|
|
361
|
+
_err(f"可用命令: {', '.join(COMMANDS.keys())}")
|
|
362
|
+
_err("运行 myagent-ai help 查看帮助")
|
|
363
|
+
sys.exit(1)
|
|
364
|
+
|
|
365
|
+
handler = COMMANDS[cmd][0]
|
|
366
|
+
rc = handler(sys.argv[2:])
|
|
367
|
+
sys.exit(rc or 0)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
if __name__ == "__main__":
|
|
371
|
+
main()
|
package/start.js
CHANGED
|
@@ -539,7 +539,7 @@ function cmdUpdate(pkgDir) {
|
|
|
539
539
|
// 1. npm 升级全局包(--force 确保跳过缓存)
|
|
540
540
|
console.log(" 正在通过 npm 更新 myagent-ai ...");
|
|
541
541
|
try {
|
|
542
|
-
execFileSync("npm", ["install", "-g", PKG_NAME, "--prefer-online", "--force"], {
|
|
542
|
+
execFileSync("npm", ["install", "-g", PKG_NAME + "@latest", "--prefer-online", "--force"], {
|
|
543
543
|
encoding: "utf8", stdio: "inherit", timeout: 120000,
|
|
544
544
|
});
|
|
545
545
|
} catch (e) {
|
|
@@ -764,6 +764,43 @@ function main() {
|
|
|
764
764
|
if (cmd === "reinstall") { cmdReinstall(pkgDir); return; }
|
|
765
765
|
if (cmd === "install") { cmdInstall(pkgDir); return; }
|
|
766
766
|
|
|
767
|
+
// [v1.22.0] CLI 子命令 — 轻量级,不需要完整启动 myagent
|
|
768
|
+
const CLI_CMDS = ["ocr", "analyze-image", "transcribe", "search", "read-url", "send-file", "help", "-h", "--help"];
|
|
769
|
+
if (CLI_CMDS.includes(cmd)) {
|
|
770
|
+
const venvPython = getVenvPython(getVenvDir());
|
|
771
|
+
const venvDir = getVenvDir();
|
|
772
|
+
const cliScript = path.join(pkgDir, "scripts", "cli.py");
|
|
773
|
+
if (!fs.existsSync(cliScript)) {
|
|
774
|
+
console.error("\x1b[31mCLI 脚本不存在: " + cliScript + "\x1b[0m");
|
|
775
|
+
process.exit(1);
|
|
776
|
+
}
|
|
777
|
+
let pyExe;
|
|
778
|
+
let env;
|
|
779
|
+
if (fs.existsSync(venvPython)) {
|
|
780
|
+
pyExe = venvPython;
|
|
781
|
+
env = {
|
|
782
|
+
...process.env,
|
|
783
|
+
VIRTUAL_ENV: venvDir,
|
|
784
|
+
PATH: `${path.join(venvDir, IS_WIN ? "Scripts" : "bin")}:${process.env.PATH}`,
|
|
785
|
+
};
|
|
786
|
+
} else {
|
|
787
|
+
pyExe = findSystemPython();
|
|
788
|
+
if (!pyExe) {
|
|
789
|
+
console.error("\x1b[31mPython 未找到,请先运行: myagent-ai install\x1b[0m");
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
env = process.env;
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
execFileSync(pyExe, [cliScript, ...args], {
|
|
796
|
+
stdio: "inherit", cwd: pkgDir, env, timeout: 300000,
|
|
797
|
+
});
|
|
798
|
+
} catch (e) {
|
|
799
|
+
process.exit(e.status || 1);
|
|
800
|
+
}
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
|
|
767
804
|
// 默认: 直接启动
|
|
768
805
|
cmdRun(pkgDir, args);
|
|
769
806
|
}
|