重构并拆分项目

This commit is contained in:
2026-06-25 17:23:11 +08:00
parent 3476d8cbcb
commit 79ec21bfd4
127 changed files with 1555 additions and 12411 deletions
-105
View File
@@ -1,105 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "amap"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-11
View File
@@ -1,11 +0,0 @@
[package]
name = "amap"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "amap"
path = "src/main.rs"
[dependencies]
serde_json = "1.0"
-18
View File
@@ -1,18 +0,0 @@
name: amap_geocode
desc: 将文字地址转换为经纬度坐标。输入地址名称返回坐标。用于"天安门的坐标是什么"等需要将地名转为坐标的场景,通常作为其他地图工具的前置步骤
type: http
tool: amap
command: geocode
path: /target/release/amap
risk_level: low
timeout_secs: 15
env:
- AMAP_KEY
params:
- name: address
required: true
desc: 详细地址名称,如"北京市朝阳区天安门"、"合肥火车站"。越详细越精确
- name: city
required: false
desc: 城市名称,可选。限定地理编码的城市范围
-15
View File
@@ -1,15 +0,0 @@
name: amap_map_link
desc: 生成高德地图可视化链接。输入中心点坐标返回可分享的地图标记链接。用于"把这个位置发给我"等需要可视化展示的场景
type: http
tool: amap
command: map_link
path: /target/release/amap
risk_level: low
timeout_secs: 15
env:
- AMAP_KEY
params:
- name: center
required: false
desc: 地图中心点经纬度坐标,格式"经度,纬度",默认天安门坐标
@@ -1,21 +0,0 @@
name: amap_poi_search
desc: 搜索指定城市内的POI地点(餐饮、酒店、景点、商场、加油站、银行等)。返回名称和地址。用于"附近有什么好吃的""北京有什么景点"等问题
type: http
tool: amap
command: poi_search
path: /target/release/amap
risk_level: low
timeout_secs: 15
env:
- AMAP_KEY
params:
- name: keywords
required: true
desc: 搜索关键词,如"咖啡厅"、"银行"、"加油站"。支持POI类型名称
- name: city
required: false
desc: 城市名称,限定搜索范围。不填则在当前城市搜索
- name: radius
required: false
desc: 搜索半径(米),如 500、1000、3000。默认 1000 米
@@ -1,15 +0,0 @@
name: amap_reverse_geocode
desc: 将经纬度坐标转换为详细文字地址。输入"经度,纬度"返回结构化地址。用于"这个坐标是哪里"等反向查询
type: http
tool: amap
command: reverse_geocode
path: /target/release/amap
risk_level: low
timeout_secs: 15
env:
- AMAP_KEY
params:
- name: location
required: true
desc: 经纬度坐标,格式为"经度,纬度",如"116.397,39.908"
@@ -1,21 +0,0 @@
name: amap_route_plan
desc: 规划两点之间的出行路线。支持驾车(driving)、步行(walking)、骑行(riding)、公交(transit)四种方式。返回距离和预计耗时。用于"从家到公司怎么走""走过去要多久"等问题
type: http
tool: amap
command: route_plan
path: /target/release/amap
risk_level: low
timeout_secs: 15
env:
- AMAP_KEY
params:
- name: origin
required: true
desc: 起点经纬度坐标,格式"经度,纬度"。可先用 amap_geocode 将地名转为坐标
- name: destination
required: true
desc: 终点经纬度坐标,格式"经度,纬度"。可先用 amap_geocode 将地名转为坐标
- name: type
required: false
desc: 出行方式:driving(驾车)、walking(步行)、riding(骑行)、transit(公交)。默认 driving
@@ -1,18 +0,0 @@
name: amap_travel_plan
desc: 为指定城市生成旅游规划。自动搜索城市内的热门景点、美食、住宿等POI。用于"北京三日游怎么安排""合肥有什么好玩的"等旅游咨询
type: http
tool: amap
command: travel_plan
path: /target/release/amap
risk_level: low
timeout_secs: 15
env:
- AMAP_KEY
params:
- name: city
required: true
desc: 目标城市名称,如"北京"、"杭州"、"成都"
- name: interests
required: false
desc: 感兴趣的关键词,逗号分隔,如"景点,美食,酒店"。默认"景点,美食"
-50
View File
@@ -1,50 +0,0 @@
fn main() {
let input: serde_json::Value = serde_json::from_reader(std::io::stdin()).unwrap();
let mode = std::env::args().nth(2).unwrap_or_default();
let p = &input["params"];
let key = match std::env::var("AMAP_KEY") {
Ok(k) if !k.is_empty() => k,
_ => {
println!(r#"{{"type":"result","content":"错误: 缺少环境变量 AMAP_KEY(高德地图 API Key),请在 .env 中设置"}}"#);
return;
}
};
if input.get("response").is_none() && input.get("db_response").is_none() {
let (url, desc) = match mode.as_str() {
"poi_search" => (format!("https://restapi.amap.com/v5/place/text?keywords={}&city={}&key={key}",
p["keywords"].as_str().unwrap_or(""), p["city"].as_str().unwrap_or("")), format!("搜索: {}", p["keywords"].as_str().unwrap_or(""))),
"geocode" => (format!("https://restapi.amap.com/v3/geocode/geo?address={}&city={}&key={key}",
p["address"].as_str().unwrap_or(""), p["city"].as_str().unwrap_or("")), format!("编码: {}", p["address"].as_str().unwrap_or(""))),
"reverse_geocode" => (format!("https://restapi.amap.com/v3/geocode/regeo?location={}&key={key}",
p["location"].as_str().unwrap_or("")), "逆编码".into()),
"route_plan" => (format!("https://restapi.amap.com/v5/direction/{}?origin={}&destination={}&key={key}",
p["type"].as_str().unwrap_or("driving"), p["origin"].as_str().unwrap_or(""), p["destination"].as_str().unwrap_or("")), "路径规划".into()),
"travel_plan" => (format!("https://restapi.amap.com/v5/place/text?keywords={}&city={}&key={key}",
p["interests"].as_str().unwrap_or(""), p["city"].as_str().unwrap_or("")), format!("旅游: {}", p["city"].as_str().unwrap_or(""))),
"map_link" => (format!("https://uri.amap.com/marker?position={}",
p["center"].as_str().unwrap_or("116.397,39.908")), "地图链接".into()),
_ => { println!(r#"{{"type":"result","content":"未知模式"}}"#); return; }
};
println!("{}", serde_json::json!({"type":"http","method":"GET","url":url,"desc":desc}));
} else {
let b = &input["response"]["body"];
let content = match mode.as_str() {
"poi_search"|"travel_plan" => {
let n = b["pois"].as_array().map(|a| a.len()).unwrap_or(0);
let items: Vec<String> = b["pois"].as_array().unwrap_or(&vec![]).iter().take(5)
.map(|x| format!("{} ({})", x["name"].as_str().unwrap_or(""), x["address"].as_str().unwrap_or(""))).collect();
format!("找到 {n} 个:\n{}", items.join("\n"))
}
"geocode" => {
let empty = vec![]; let g = b["geocodes"].as_array().unwrap_or(&empty);
g.first().map(|x| format!("{}{}", x["formatted_address"].as_str().unwrap_or(""), x["location"].as_str().unwrap_or(""))).unwrap_or("未找到".into())
}
"reverse_geocode" => b["regeocode"]["formatted_address"].as_str().unwrap_or("未找到").into(),
"route_plan" => format!("距离: {}m, 耗时: {}s", b["route"]["distance"].as_str().unwrap_or("?"), b["route"]["duration"].as_str().unwrap_or("?")),
"map_link" => "地图链接已生成".into(),
_ => format!("{b}"),
};
println!("{}", serde_json::json!({"type":"result","content":content}));
}
}
-18
View File
@@ -1,18 +0,0 @@
#!/bin/bash
# 构建所有工具,产物统一收集到 bin/ 供长期沙箱容器挂载
set -e
cd "$(dirname "$0")"
TOOLS=(datetime weather amap web_search fetch_page memories tool_manager)
mkdir -p bin
echo "=== 构建工具 ==="
for tool in "${TOOLS[@]}"; do
echo " $tool..."
(cd "$tool" && cargo build --release --quiet)
cp "$tool/target/release/$tool" "bin/$tool"
done
echo "=== 完成 ==="
ls -lh bin/*
-382
View File
@@ -1,382 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "cc"
version = "1.2.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "datetime"
version = "0.1.0"
dependencies = [
"chrono",
"serde_json",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "log"
version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wasm-bindgen"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-12
View File
@@ -1,12 +0,0 @@
[package]
name = "datetime"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "datetime"
path = "src/main.rs"
[dependencies]
chrono = "0.4"
serde_json = "1.0"
@@ -1,9 +0,0 @@
name: get_current_datetime
desc: 获取当前系统本地日期时间,返回年月日、星期、时区。用于"现在几点""今天几号""星期几"等问题
type: stdio
tool: datetime
path: /target/release/datetime
risk_level: low
timeout_secs: 3
params: []
-14
View File
@@ -1,14 +0,0 @@
//! datetime — 本地时间
use chrono::Local;
fn main() {
let _ = std::io::stdin().read_line(&mut String::new());
let now = Local::now();
let out = serde_json::json!({
"type": "result",
"content": format!("当前时间: {}\n星期: {}\n时区: {}",
now.format("%Y-%m-%d %H:%M:%S"), now.format("%A"), now.offset())
});
println!("{out}");
}
-105
View File
@@ -1,105 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "fetch_page"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-11
View File
@@ -1,11 +0,0 @@
[package]
name = "fetch_page"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "fetch_page"
path = "src/main.rs"
[dependencies]
serde_json = "1.0"
@@ -1,12 +0,0 @@
name: fetch_page
desc: 抓取指定URL网页并自动提取纯文本正文内容,过滤广告、导航、脚本等无关元素。返回最多30行文本。用于"帮我看看这个网页说了什么"等需要阅读网页内容的场景
type: http
tool: fetch_page
path: /target/release/fetch_page
risk_level: low
timeout_secs: 30
params:
- name: url
required: true
desc: 网页URL
-15
View File
@@ -1,15 +0,0 @@
fn main() {
let input: serde_json::Value = serde_json::from_reader(std::io::stdin()).unwrap();
if input.get("response").is_none() && input.get("db_response").is_none() {
let url = input["params"]["url"].as_str().unwrap_or("");
if url.is_empty() { println!(r#"{{"type":"result","content":"缺少url"}}"#); return; }
println!("{}", serde_json::json!({"type":"http","method":"GET","url":url,"desc":format!("抓取 {url}")}));
} else {
let html = input["response"]["body"].as_str().unwrap_or("");
let mut r = String::new(); let mut tag = false;
for c in html.chars() { if c=='<' { tag=true; } else if c=='>' { tag=false; } else if !tag { r.push(c); } }
let text = r.replace("&nbsp;"," ").replace("&lt;","<").replace("&gt;",">").replace("&amp;","&");
let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).take(30).collect();
println!("{}", serde_json::json!({"type":"result","content":lines.join("\n")}));
}
}
-219
View File
@@ -1,219 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libredox"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "memories"
version = "0.1.0"
dependencies = [
"dirs",
"serde",
"serde_json",
]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_users"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "memories"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "memories"
path = "src/main.rs"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
dirs = "6.0"
@@ -1,15 +0,0 @@
name: delete_memory
desc: 按ID删除指定的长期记忆。用于"忘记我住哪里""删除那条记录"等清除记忆的请求
type: stdio
tool: memories
command: delete
path: /target/release/memories
risk_level: high
timeout_secs: 5
env:
- IAS_MEMORY_DIR
params:
- name: memory_id
required: true
desc: 记忆ID
-15
View File
@@ -1,15 +0,0 @@
name: get_memory
desc: 按ID查看指定的单条记忆详情。用于需要确认某条具体记忆内容的场景
type: stdio
tool: memories
command: get
path: /target/release/memories
risk_level: low
timeout_secs: 3
env:
- IAS_MEMORY_DIR
params:
- name: memory_id
required: true
desc: 记忆的数字ID,从 read_memories 返回结果中获取
@@ -1,12 +0,0 @@
name: read_memories
desc: 读取用户的所有长期记忆。返回之前记录的个人偏好、习惯、重要信息等。用于"你还记得我住哪吗""我之前说过喜欢什么"等回忆类问题
type: stdio
tool: memories
command: list
path: /target/release/memories
risk_level: low
timeout_secs: 3
env:
- IAS_MEMORY_DIR
params:
@@ -1,18 +0,0 @@
name: update_memory
desc: 按ID更新指定记忆的内容。用于"我搬家了,更新地址""把我名字改成XX"等修改记忆的请求
type: stdio
tool: memories
command: update
path: /target/release/memories
risk_level: high
timeout_secs: 5
env:
- IAS_MEMORY_DIR
params:
- name: memory_id
required: true
desc: 记忆ID
- name: content
required: true
desc: 更新后的新内容,覆盖原记忆
@@ -1,15 +0,0 @@
name: write_memory
desc: 记录一条新的长期记忆。当用户透露个人偏好、习惯、重要信息时使用此工具
type: stdio
tool: memories
command: add
path: /target/release/memories
risk_level: high
timeout_secs: 5
env:
- IAS_MEMORY_DIR
params:
- name: content
required: true
desc: 要记录的记忆内容,用自然语言描述。如"用户住在合肥包河区联投新安里A区"
-37
View File
@@ -1,37 +0,0 @@
//! memories — 长期记忆管理(通过主进程 DB 代理)
fn main() {
let input: serde_json::Value = serde_json::from_reader(std::io::stdin()).unwrap();
let mode = std::env::args().nth(2).unwrap_or_else(|| "list".to_string());
let params = &input["params"];
if input.get("db_response").is_none() {
// 发出 DB 请求
match mode.as_str() {
"list" => println!("{}", serde_json::json!({"type":"db","operation":"read_memories","desc":"读取记忆"})),
"add" => {
let c = params["content"].as_str().unwrap_or("");
if c.is_empty() { println!(r#"{{"type":"result","content":"缺少content"}}"#); return; }
println!("{}", serde_json::json!({"type":"db","operation":"write_memory","params":{"content":c},"desc":format!("记录: {c}")}));
}
"get" => {
let id = params["memory_id"].as_u64().unwrap_or(0);
println!("{}", serde_json::json!({"type":"db","operation":"read_memories","params":{"memory_id":id},"desc":"读取记忆"}));
}
"delete" => {
let id = params["memory_id"].as_u64().unwrap_or(0);
println!("{}", serde_json::json!({"type":"db","operation":"delete_memory","params":{"memory_id":id},"desc":format!("删除记忆 {id}")}));
}
"update" => {
let id = params["memory_id"].as_u64().unwrap_or(0);
let c = params["content"].as_str().unwrap_or("");
println!("{}", serde_json::json!({"type":"db","operation":"update_memory","params":{"memory_id":id,"content":c},"desc":format!("更新记忆 {id}")}));
}
_ => println!(r#"{{"type":"result","content":"未知操作"}}"#),
}
} else {
// 处理 DB 响应
let result = input["db_response"]["result"].as_str().unwrap_or("");
println!("{}", serde_json::json!({"type":"result","content":result}));
}
}
-105
View File
@@ -1,105 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tool_inspector"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-11
View File
@@ -1,11 +0,0 @@
[package]
name = "tool_inspector"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "tool_inspector"
path = "src/main.rs"
[dependencies]
serde_json = "1.0"
@@ -1,17 +0,0 @@
name: tool_inspector
desc: 只读查看本地工具的源码、Cargo.toml 和 spec 配置。在用 tool_manager 修改某工具前,先用本工具查看其当前实现,避免盲目覆盖丢失逻辑。低风险,无需审批
type: stdio
tool: tool_inspector
path: /target/release/tool_inspector
risk_level: low
timeout_secs: 10
params:
- name: action
required: true
desc: 操作类型:list(列出所有工具及文件构成)、read_tool(读取某工具的全部 Cargo.toml/specs/src 源码)、read_file(读取工具目录下单个文件)
- name: tool_name
required: false
desc: 工具名称(read_tool/read_file 必填),只允许字母、数字、下划线和短横线
- name: file
required: false
desc: read_file 时的相对路径,如 src/main.rs、Cargo.toml、specs/weather.tool.yaml;必须是相对路径,禁止 .. 和 target 目录
-270
View File
@@ -1,270 +0,0 @@
//! tool_inspector — 只读查看本地工具的源码与配置
//!
//! 用途:在用 tool_manager 修改工具前,先用本工具查看当前实现,避免盲目覆盖丢失逻辑。
//!
//! 安全约束:
//! - 纯只读:不写文件、不执行命令、不联网
//! - tool_name 与相对路径严格校验,禁止路径穿越
//! - 跳过 target/ 构建产物目录
//! - 低风险(risk_level: low),无需用户审批
use serde_json::{Value, json};
use std::fs;
use std::path::{Component, Path, PathBuf};
fn main() {
let input: Value = match serde_json::from_reader(std::io::stdin()) {
Ok(v) => v,
Err(e) => return result(&format!("读取输入失败: {e}")),
};
let params = &input["params"];
let action = params["action"].as_str().unwrap_or("");
let out = match run(action, params) {
Ok(s) => s,
Err(e) => format!("查看失败: {e}"),
};
result(&out);
}
fn run(action: &str, params: &Value) -> Result<String, String> {
let tools_root = locate_tools_root()?;
match action {
"list" => list_tools(&tools_root),
"read_tool" => {
let name = required_str(params, "tool_name")?;
validate_tool_name(name)?;
read_tool(&tools_root, name)
}
"read_file" => {
let name = required_str(params, "tool_name")?;
validate_tool_name(name)?;
let file = required_str(params, "file")?;
read_file(&tools_root, name, file)
}
_ => Err("action 必须是 list、read_tool 或 read_file".to_string()),
}
}
/// 列出 tools 目录下所有工具及其文件构成
fn list_tools(tools_root: &Path) -> Result<String, String> {
let mut names: Vec<String> = Vec::new();
for entry in fs::read_dir(tools_root).map_err(|e| e.to_string())? {
let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path();
if !path.is_dir() {
continue;
}
let name = entry.file_name().to_string_lossy().to_string();
// 跳过产物目录与隐藏目录
if name == "bin" || name.starts_with('.') {
continue;
}
// 只有含 specs/src/Cargo.toml 之一的目录才视为工具
if path.join("specs").exists()
|| path.join("src").exists()
|| path.join("Cargo.toml").exists()
{
names.push(name);
}
}
names.sort();
if names.is_empty() {
return Ok("tools 目录下没有工具".into());
}
let mut out = format!("本地工具列表({} 个):\n", names.len());
for n in &names {
let dir = tools_root.join(n);
let mark = |has: bool| if has { "" } else { "" };
out.push_str(&format!(
"{n} (src:{} specs:{} Cargo.toml:{})\n",
mark(dir.join("src").exists()),
mark(dir.join("specs").exists()),
mark(dir.join("Cargo.toml").exists()),
));
}
out.push_str("\n用 read_tool 查看某工具的全部源码与配置,或用 read_file 读取单个文件。");
Ok(out)
}
/// 一次性读取某工具的 Cargo.toml + 所有 specs + src 下所有 .rs 文件
fn read_tool(tools_root: &Path, name: &str) -> Result<String, String> {
let tool_dir = tools_root.join(name);
if !tool_dir.exists() {
return Err(format!("工具 {name} 不存在"));
}
let mut out = String::new();
if let Ok(c) = fs::read_to_string(tool_dir.join("Cargo.toml")) {
out.push_str(&format!("===== {name}/Cargo.toml =====\n{c}\n\n"));
}
// specs/*.tool.yaml(可能有多个文件),按文件名排序
let specs_dir = tool_dir.join("specs");
if specs_dir.exists() {
let mut specs: Vec<PathBuf> = fs::read_dir(&specs_dir)
.map_err(|e| e.to_string())?
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| matches!(p.extension().and_then(|e| e.to_str()), Some("yaml" | "yml")))
.collect();
specs.sort();
for spec in specs {
let rel = spec.strip_prefix(&tool_dir).unwrap_or(&spec).display().to_string();
match fs::read_to_string(&spec) {
Ok(c) => out.push_str(&format!("===== {name}/{rel} =====\n{c}\n\n")),
Err(e) => out.push_str(&format!("===== {name}/{rel} =====\n(读取失败: {e}\n\n")),
}
}
}
// src 下所有 .rs 文件(递归,支持多模块工具)
let mut src_files: Vec<PathBuf> = Vec::new();
collect_rs_files(&tool_dir.join("src"), &mut src_files)?;
if src_files.is_empty() {
out.push_str(&format!("{name} 无 src/*.rs 文件)\n"));
} else {
src_files.sort();
for f in src_files {
let rel = f.strip_prefix(&tool_dir).unwrap_or(&f).display().to_string();
match fs::read_to_string(&f) {
Ok(c) => out.push_str(&format!("===== {name}/{rel} =====\n{c}\n\n")),
Err(e) => out.push_str(&format!("===== {name}/{rel} =====\n(读取失败: {e}\n\n")),
}
}
}
if out.is_empty() {
return Err(format!("工具 {name} 没有可读的源码或配置"));
}
Ok(out.trim_end().to_string())
}
/// 读取工具目录下指定的单个文件(严格防路径穿越)
fn read_file(tools_root: &Path, name: &str, file: &str) -> Result<String, String> {
let tool_dir = tools_root.join(name);
if !tool_dir.exists() {
return Err(format!("工具 {name} 不存在"));
}
let rel = sanitize_rel_path(file)?;
let target = tool_dir.join(&rel);
// 规范化后必须仍在 tool_dir 内(双重防穿越)
let canonical_tool = tool_dir.canonicalize().map_err(|e| e.to_string())?;
let canonical_target = target
.canonicalize()
.map_err(|_| format!("文件不存在: {file}"))?;
if !canonical_target.starts_with(&canonical_tool) {
return Err("路径越界,禁止访问工具目录之外的文件".into());
}
if canonical_target.is_dir() {
return Err(format!("{file} 是目录,请用 read_tool 查看或指定具体文件"));
}
let content = fs::read_to_string(&canonical_target)
.map_err(|e| format!("读取 {file} 失败: {e}"))?;
Ok(format!("===== {name}/{file} =====\n{content}"))
}
/// 递归收集 .rs 文件,跳过隐藏目录
fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) -> Result<(), String> {
if !dir.exists() {
return Ok(());
}
for entry in fs::read_dir(dir).map_err(|e| e.to_string())? {
let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path();
if path.is_dir() {
if !entry.file_name().to_string_lossy().starts_with('.') {
collect_rs_files(&path, out)?;
}
} else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
out.push(path);
}
}
Ok(())
}
/// 校验相对路径:禁止绝对路径、禁止 ..、禁止 target 构建产物
fn sanitize_rel_path(file: &str) -> Result<PathBuf, String> {
if file.is_empty() {
return Err("file 不能为空".into());
}
let p = PathBuf::from(file);
for comp in p.components() {
match comp {
Component::Normal(_) | Component::CurDir => {}
Component::ParentDir => return Err("file 禁止包含 ..".into()),
Component::RootDir | Component::Prefix(_) => {
return Err("file 必须是相对路径".into())
}
}
}
if p.starts_with("target") {
return Err("禁止读取 target 构建产物目录".into());
}
Ok(p)
}
fn validate_tool_name(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("缺少 tool_name".into());
}
if !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
return Err("tool_name 只允许字母、数字、下划线和短横线".into());
}
Ok(())
}
fn required_str<'a>(params: &'a Value, key: &str) -> Result<&'a str, String> {
params[key]
.as_str()
.filter(|s| !s.is_empty())
.ok_or_else(|| format!("缺少参数 {key}"))
}
/// 定位 tools 根目录。
///
/// 产物可能位于两处之一:
/// - `tools/bin/tool_inspector`(统一产物目录,registry 优先使用)
/// - `tools/tool_inspector/target/release/tool_inspector`(构建产物)
///
/// 因此不依赖固定向上层数,而是从 exe 向上找第一个“含 bin/ 子目录且至少含一个
/// 工具子目录”的祖先,即为 tools 根。对两种位置均正确。
fn locate_tools_root() -> Result<PathBuf, String> {
let exe = std::env::current_exe().map_err(|e| e.to_string())?;
let mut cur = exe.parent();
while let Some(dir) = cur {
if dir.join("bin").is_dir() && has_tool_subdir(dir) {
return Ok(dir.to_path_buf());
}
cur = dir.parent();
}
Err("无法定位 tools 根目录".into())
}
/// 判断目录下是否存在至少一个工具子目录(含 specs/Cargo.toml/src 之一)
fn has_tool_subdir(dir: &Path) -> bool {
let Ok(entries) = fs::read_dir(dir) else {
return false;
};
for entry in entries.flatten() {
let p = entry.path();
if p.is_dir()
&& (p.join("specs").exists()
|| p.join("Cargo.toml").exists()
|| p.join("src").exists())
{
return true;
}
}
false
}
fn result(content: &str) {
println!("{}", json!({"type":"result","content":content}));
}
-105
View File
@@ -1,105 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tool_manager"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-11
View File
@@ -1,11 +0,0 @@
[package]
name = "tool_manager"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "tool_manager"
path = "src/main.rs"
[dependencies]
serde_json = "1.0"
@@ -1,36 +0,0 @@
name: tool_manager
desc: 创建、修改和构建本地能力工具。用于给助手新增或更新 tools 目录下的 stdio Rust 工具;高风险操作,执行前需要用户确认。
type: stdio
tool: tool_manager
path: /target/release/tool_manager
risk_level: high
timeout_secs: 300
env:
- HOME
- CARGO_HOME
- RUSTUP_HOME
params:
- name: action
required: true
desc: 操作类型:create_tool、update_tool 或 build_tool
- name: tool_name
required: true
desc: 工具名称,只允许字母、数字、下划线和短横线
- name: description
required: false
desc: 工具说明,创建或更新 spec 时使用
- name: rust_code
required: false
desc: 完整的 src/main.rs Rust 源码;省略时 create_tool 会生成默认模板
- name: params
required: false
desc: 参数列表数组,每项包含 name、required、desc
- name: risk_level
required: false
desc: 已弃用,tool_manager 创建/更新的工具一律强制 high risk,传入值将被忽略
- name: timeout_secs
required: false
desc: 新工具超时时间秒数,默认 30
- name: overwrite
required: false
desc: create_tool 目标已存在时是否覆盖,默认 false
-397
View File
@@ -1,397 +0,0 @@
//! tool_manager — 受控创建、修改、构建本地工具
//!
//! 安全约束:
//! - 不允许操作保留工具名(含 `tool_manager` 自身),防止自改后门
//! - 由本工具创建/更新的工具一律 `risk_level: high`,忽略调用方传入值
//! - 所有调用追加写入 `tools/.tool_audit.log` 审计日志
//! - 构建使用 `timeout` 限制时长,失败时清理残留二进制,避免调用旧版本"假成功"
use serde_json::{Value, json};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
/// 保留工具名 —— 禁止 create/update/build
const RESERVED_NAMES: &[&str] = &["target", "specs", "src", ".", "..", "tool_manager"];
/// 构建超时(秒)。需小于 tool_manager 自身的 timeout_secs。
const BUILD_TIMEOUT_SECS: u64 = 180;
fn main() {
let input: Value = match serde_json::from_reader(std::io::stdin()) {
Ok(v) => v,
Err(e) => return result(&format!("读取输入失败: {e}")),
};
let params = &input["params"];
let user_id = input["user_id"].as_str().unwrap_or("unknown");
let action = params["action"].as_str().unwrap_or("");
let tool_name = params["tool_name"].as_str().unwrap_or("");
let (output, success) = match run(action, tool_name, params) {
Ok(msg) => (msg, true),
Err(e) => (format!("工具管理失败: {e}"), false),
};
// 审计日志(best effort,不影响主流程)
if let Ok(tools_root) = locate_tools_root() {
audit(&tools_root, user_id, action, tool_name, success, &output);
}
result(&output);
}
fn run(action: &str, tool_name: &str, params: &Value) -> Result<String, String> {
validate_tool_name(tool_name)?;
let tools_root = locate_tools_root()?;
let tool_dir = tools_root.join(tool_name);
match action {
"create_tool" => {
let overwrite = truthy(params.get("overwrite"));
if tool_dir.exists() && !overwrite {
return Err(format!(
"工具 {tool_name} 已存在;如需覆盖,请设置 overwrite=true"
));
}
write_tool_project(&tool_dir, tool_name, params)?;
build_tool(&tool_dir, tool_name)?;
Ok(format!(
"已创建并构建工具 {tool_name}risk_level 强制为 high)。可通过 query_capabilities 查看并用 call_capability 调用。"
))
}
"update_tool" => {
if !tool_dir.exists() {
return Err(format!("工具 {tool_name} 不存在,无法修改"));
}
update_tool_project(&tool_dir, tool_name, params)?;
build_tool(&tool_dir, tool_name)?;
Ok(format!(
"已修改并构建工具 {tool_name}risk_level 强制为 high)。注册表重载后即可使用最新版本。"
))
}
"build_tool" => {
if !tool_dir.exists() {
return Err(format!("工具 {tool_name} 不存在,无法构建"));
}
build_tool(&tool_dir, tool_name)?;
Ok(format!("已构建工具 {tool_name}"))
}
_ => Err("action 必须是 create_tool、update_tool 或 build_tool".to_string()),
}
}
fn write_tool_project(tool_dir: &Path, tool_name: &str, params: &Value) -> Result<(), String> {
fs::create_dir_all(tool_dir.join("src")).map_err(|e| e.to_string())?;
fs::create_dir_all(tool_dir.join("specs")).map_err(|e| e.to_string())?;
fs::write(tool_dir.join("Cargo.toml"), cargo_toml(tool_name)).map_err(|e| e.to_string())?;
fs::write(tool_dir.join("src/main.rs"), rust_code(tool_name, params))
.map_err(|e| e.to_string())?;
fs::write(
tool_dir
.join("specs")
.join(format!("{tool_name}.tool.yaml")),
spec_yaml(tool_name, params),
)
.map_err(|e| e.to_string())?;
Ok(())
}
fn update_tool_project(tool_dir: &Path, tool_name: &str, params: &Value) -> Result<(), String> {
if let Some(code) = params["rust_code"].as_str() {
fs::write(tool_dir.join("src/main.rs"), code).map_err(|e| e.to_string())?;
}
// spec 一旦由 tool_manager 管理,risk_level 恒为 high
let should_update_spec = params.get("description").is_some()
|| params.get("params").is_some()
|| params.get("risk_level").is_some()
|| params.get("timeout_secs").is_some();
if should_update_spec {
fs::create_dir_all(tool_dir.join("specs")).map_err(|e| e.to_string())?;
fs::write(
tool_dir
.join("specs")
.join(format!("{tool_name}.tool.yaml")),
spec_yaml(tool_name, params),
)
.map_err(|e| e.to_string())?;
}
Ok(())
}
fn build_tool(tool_dir: &Path, tool_name: &str) -> Result<(), String> {
let binary = tool_dir.join("target/release").join(tool_name);
let use_builder = std::env::var("IAS_DOCKER_BUILDER")
.ok()
.as_deref()
== Some("1");
let (uid, gid) = current_uid_gid();
// 启用 builder 容器时在隔离环境内离线编译,挂载 tool_dir 到 /work,产物写回宿主;
// 否则本地 cargo(受 timeout 限制)
let output = if use_builder {
Command::new("docker")
.arg("run").arg("--rm")
.arg("--network").arg("none")
.arg("--memory").arg("1g")
.arg("--cpus").arg("1")
.arg("--pids-limit").arg("128")
.arg("--cap-drop").arg("ALL")
.arg("--security-opt").arg("no-new-privileges")
.arg("--user").arg(format!("{}:{}", uid, gid))
.arg("-v").arg(format!("{}:/work", tool_dir.display()))
.arg("-w").arg("/work")
.arg("-e").arg("CARGO_NET_OFFLINE=true")
.arg("-e").arg("CARGO_HOME=/tmp/cargo")
.arg("ias-tool-builder")
.arg("timeout").arg(BUILD_TIMEOUT_SECS.to_string())
.arg("cargo").arg("build").arg("--release").arg("--offline")
.output()
} else {
Command::new("timeout")
.arg(BUILD_TIMEOUT_SECS.to_string())
.arg("cargo")
.arg("build")
.arg("--release")
.current_dir(tool_dir)
.output()
}
.map_err(|e| format!("启动构建失败: {e}"))?;
if !output.status.success() {
// 构建失败:删除可能残留的旧二进制,避免 reload 后仍调用旧版本造成"假成功"
let _ = fs::remove_file(&binary);
let code = output.status.code().unwrap_or(-1);
let stderr = String::from_utf8_lossy(&output.stderr);
let reason = if code == 124 {
format!("构建超时({BUILD_TIMEOUT_SECS}s")
} else {
stderr.trim().to_string()
};
return Err(format!("cargo build 失败(退出码 {code}): {reason}"));
}
if !binary.exists() {
return Err(format!("构建后未找到二进制: {}", binary.display()));
}
// 复制到统一产物目录 tools/bin/,供长期沙箱容器挂载使用
let tools_root = tool_dir
.parent()
.ok_or_else(|| "无法定位 tools 根目录".to_string())?;
let bin_dir = tools_root.join("bin");
fs::create_dir_all(&bin_dir).map_err(|e| format!("创建产物目录失败: {e}"))?;
fs::copy(&binary, bin_dir.join(tool_name))
.map_err(|e| format!("复制产物到 bin 失败: {e}"))?;
Ok(())
}
/// 定位 tools 根目录。
///
/// 产物可能位于两处之一:
/// - `tools/bin/tool_manager`(统一产物目录,registry 优先使用)
/// - `tools/tool_manager/target/release/tool_manager`(构建产物)
///
/// 因此不依赖固定向上层数,而是从 exe 向上找第一个“含 bin/ 子目录且至少含一个
/// 工具子目录”的祖先,即为 tools 根。对两种位置均正确。
fn locate_tools_root() -> Result<PathBuf, String> {
let exe = std::env::current_exe().map_err(|e| e.to_string())?;
let mut cur = exe.parent();
while let Some(dir) = cur {
if dir.join("bin").is_dir() && has_tool_subdir(dir) {
return Ok(dir.to_path_buf());
}
cur = dir.parent();
}
Err("无法定位 tools 根目录".into())
}
/// 判断目录下是否存在至少一个工具子目录(含 specs/Cargo.toml/src 之一)
fn has_tool_subdir(dir: &Path) -> bool {
let Ok(entries) = fs::read_dir(dir) else {
return false;
};
for entry in entries.flatten() {
let p = entry.path();
if p.is_dir()
&& (p.join("specs").exists()
|| p.join("Cargo.toml").exists()
|| p.join("src").exists())
{
return true;
}
}
false
}
fn cargo_toml(tool_name: &str) -> String {
format!(
r#"[package]
name = "{tool_name}"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "{tool_name}"
path = "src/main.rs"
[dependencies]
serde_json = "1.0"
"#
)
}
fn rust_code(tool_name: &str, params: &Value) -> String {
params["rust_code"]
.as_str()
.map(str::to_string)
.unwrap_or_else(|| default_rust_code(tool_name))
}
fn default_rust_code(tool_name: &str) -> String {
format!(
r#"//! {tool_name} — generated tool
fn main() {{
let input: serde_json::Value = serde_json::from_reader(std::io::stdin()).unwrap_or_default();
let content = format!("工具 {tool_name} 已收到参数: {{}}", input["params"]);
println!("{{}}", serde_json::json!({{"type":"result","content":content}}));
}}
"#
)
}
fn spec_yaml(tool_name: &str, params: &Value) -> String {
// 安全约束:tool_manager 产出的工具一律高风险,忽略调用方传入的 risk_level
let risk_level = "high";
let timeout_secs = params["timeout_secs"].as_u64().unwrap_or(30).clamp(1, 300);
let desc = params["description"]
.as_str()
.unwrap_or("由 tool_manager 创建的本地工具(自动标记 high risk)");
let mut out = format!(
"name: {tool_name}\n\
desc: {}\n\
type: stdio\n\
tool: {tool_name}\n\
path: /target/release/{tool_name}\n\
risk_level: {risk_level}\n\
timeout_secs: {timeout_secs}\n",
yaml_scalar(desc)
);
if let Some(items) = params["params"].as_array() {
out.push_str("params:\n");
for item in items {
let Some(name) = item["name"].as_str() else {
continue;
};
if validate_param_name(name).is_err() {
continue;
}
let required = item["required"].as_bool().unwrap_or(false);
let desc = item["desc"].as_str().unwrap_or("");
out.push_str(&format!(
" - name: {name}\n required: {required}\n desc: {}\n",
yaml_scalar(desc)
));
}
} else {
out.push_str("params: []\n");
}
// 安全约束:tool_manager 产出的工具不受信任(含 LLM 提供的 rust_code),
// 默认进 stdio 沙箱容器(无网络、只读根),防止数据外泄/越权文件读
out.push_str("sandbox:\n profile: stdio\n");
out
}
fn validate_tool_name(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("缺少 tool_name".to_string());
}
if RESERVED_NAMES.contains(&name) {
return Err(format!(
"tool_name '{name}' 是保留名称,禁止 create/update/build"
));
}
if !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
return Err("tool_name 只允许字母、数字、下划线和短横线".to_string());
}
Ok(())
}
fn validate_param_name(name: &str) -> Result<(), String> {
if name.is_empty()
|| !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
return Err("参数名非法".to_string());
}
Ok(())
}
fn yaml_scalar(value: &str) -> String {
serde_json::to_string(value).unwrap_or_else(|_| "\"\"".to_string())
}
fn truthy(value: Option<&Value>) -> bool {
match value {
Some(Value::Bool(v)) => *v,
Some(Value::String(v)) => matches!(v.as_str(), "true" | "1" | "yes" | ""),
_ => false,
}
}
/// 获取当前进程的 uid:gid,用于 builder 容器 --user,使容器内进程能写 bind mount 的宿主目录
fn current_uid_gid() -> (String, String) {
let uid = String::from_utf8_lossy(
&Command::new("id").arg("-u").output().map(|o| o.stdout).unwrap_or_default(),
)
.trim()
.to_string();
let gid = String::from_utf8_lossy(
&Command::new("id").arg("-g").output().map(|o| o.stdout).unwrap_or_default(),
)
.trim()
.to_string();
(uid, gid)
}
/// 追加审计日志到 tools 根目录下的 .tool_audit.log
fn audit(
tools_root: &Path,
user_id: &str,
action: &str,
tool_name: &str,
success: bool,
detail: &str,
) {
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
// detail 可能很长,截断到 200 字节避免日志膨胀
let detail: String = detail.chars().take(200).collect();
let line = format!(
"{ts}\t{user_id}\t{action}\t{tool_name}\t{}\t{detail}\n",
if success { "ok" } else { "err" }
);
let path = tools_root.join(".tool_audit.log");
if let Ok(mut f) = fs::OpenOptions::new().create(true).append(true).open(&path) {
use std::io::Write;
let _ = f.write_all(line.as_bytes());
}
}
fn result(content: &str) {
println!("{}", json!({"type":"result","content":content}));
}
-105
View File
@@ -1,105 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "weather"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-11
View File
@@ -1,11 +0,0 @@
[package]
name = "weather"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "weather"
path = "src/main.rs"
[dependencies]
serde_json = "1.0"
@@ -1,15 +0,0 @@
name: weather_forecast
desc: 查询指定城市未来7天的每日天气预报(白天/夜间天气、最高/最低温度)。用于"明天天气""这周天气"等问题
type: http
tool: weather
command: forecast
path: /target/release/weather
risk_level: low
timeout_secs: 10
env:
- QWEATHER_KEY
params:
- name: location
required: true
desc: 城市名称,如"北京"、"上海"、"合肥"。支持地级市和县级市
@@ -1,15 +0,0 @@
name: weather_hourly
desc: 查询指定城市未来24小时逐小时天气(温度、天气状况、风力、降水概率)。用于"今晚会下雨吗""下午几点最热"等精确时间天气问题
type: http
tool: weather
command: hourly
path: /target/release/weather
risk_level: low
timeout_secs: 10
env:
- QWEATHER_KEY
params:
- name: location
required: true
desc: 城市名称,如"北京"、"上海"、"合肥"。支持地级市和县级市
-15
View File
@@ -1,15 +0,0 @@
name: weather_now
desc: 查询指定城市的实时天气(温度、体感温度、风向风力、湿度、气压、能见度)。用于回答"今天天气怎么样""北京多少度"等即时天气问题
type: http
tool: weather
command: now
path: /target/release/weather
risk_level: low
timeout_secs: 10
env:
- QWEATHER_KEY
params:
- name: location
required: true
desc: 城市名称,如"北京"、"上海"、"合肥"。支持地级市和县级市
-49
View File
@@ -1,49 +0,0 @@
//! weather — 和风天气
const HOST: &str = "https://devapi.qweather.com/v7";
fn main() {
let input: serde_json::Value = serde_json::from_reader(std::io::stdin()).unwrap();
let mode = std::env::args().nth(2).unwrap_or_else(|| "now".to_string());
let loc = input["params"]["location"].as_str().unwrap_or("北京");
let key = match std::env::var("QWEATHER_KEY") {
Ok(k) if !k.is_empty() => k,
_ => {
println!(r#"{{"type":"result","content":"错误: 缺少环境变量 QWEATHER_KEY(和风天气 API Key),请在 .env 中设置"}}"#);
return;
}
};
if input.get("response").is_none() && input.get("db_response").is_none() {
let (path, desc) = match mode.as_str() {
"now" => ("weather/now", "实时天气"),
"forecast" => ("weather/7d", "7天预报"),
"hourly" => ("weather/24h", "24h逐时"),
_ => { println!(r#"{{"type":"result","content":"未知模式"}}"#); return; }
};
println!("{}", serde_json::json!({
"type":"http","method":"GET",
"url":format!("{HOST}/{path}?location={loc}&key={key}"),
"desc":desc
}));
} else {
let body = &input["response"]["body"];
let ok = body["code"].as_str() == Some("200");
let content = if !ok { "查询失败".into() } else { match mode.as_str() {
"now" => format!("{} {}°C(体感{}°C) {}{}级 湿度{}%",
body["now"]["text"].as_str().unwrap_or("?"), body["now"]["temp"].as_str().unwrap_or("?"),
body["now"]["feelsLike"].as_str().unwrap_or("?"), body["now"]["windDir"].as_str().unwrap_or("?"),
body["now"]["windScale"].as_str().unwrap_or("?"), body["now"]["humidity"].as_str().unwrap_or("?")),
"forecast" => body["daily"].as_array().unwrap_or(&vec![]).iter().take(7)
.map(|d| format!("{}: {}~{}°C", d["fxDate"].as_str().unwrap_or(""),
d["tempMin"].as_str().unwrap_or(""), d["tempMax"].as_str().unwrap_or("")))
.collect::<Vec<_>>().join("\n"),
"hourly" => body["hourly"].as_array().unwrap_or(&vec![]).iter().take(12)
.map(|h| format!("{}: {} {}°C", h["fxTime"].as_str().unwrap_or(""),
h["text"].as_str().unwrap_or(""), h["temp"].as_str().unwrap_or("")))
.collect::<Vec<_>>().join("\n"),
_ => String::new(),
}};
println!("{}", serde_json::json!({"type":"result","content":content}));
}
}
-105
View File
@@ -1,105 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "web_search"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-11
View File
@@ -1,11 +0,0 @@
[package]
name = "web_search"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "web_search"
path = "src/main.rs"
[dependencies]
serde_json = "1.0"
@@ -1,16 +0,0 @@
name: web_search
desc: 联网搜索互联网最新信息。支持 AI 摘要,返回标题、URL和内容摘要。用于查询实时资讯、最新事实、热点新闻等。需要 TAVILY_API_KEY
type: http
tool: web_search
path: /target/release/web_search
risk_level: low
timeout_secs: 30
env:
- TAVILY_API_KEY
params:
- name: query
required: true
desc: 搜索关键词,如"Rust 2024 edition 新特性"、"今天比特币价格"
- name: max_results
desc: 返回的最大搜索结果数量(1-20),默认5条
-22
View File
@@ -1,22 +0,0 @@
fn main() {
let input: serde_json::Value = serde_json::from_reader(std::io::stdin()).unwrap();
if input.get("response").is_none() && input.get("db_response").is_none() {
let q = input["params"]["query"].as_str().unwrap_or("");
if q.is_empty() { println!(r#"{{"type":"result","content":"缺少query"}}"#); return; }
let n = input["params"]["max_results"].as_u64().unwrap_or(5);
let body = serde_json::json!({"query":q,"max_results":n,"include_answer":true});
println!("{}", serde_json::json!({
"type":"http","method":"POST","url":"https://api.tavily.com/search",
"body":body.to_string(),"desc":format!("搜索: {q}")
}));
} else {
let b = &input["response"]["body"];
let mut o = String::new();
if let Some(a) = b["answer"].as_str() { o.push_str(&format!("📝 {a}\n\n")); }
for (i,r) in b["results"].as_array().unwrap_or(&vec![]).iter().enumerate() {
o.push_str(&format!("{}. {} - {}\n", i+1, r["title"].as_str().unwrap_or(""), r["url"].as_str().unwrap_or("")));
}
if o.is_empty() { o = "未找到结果".into(); }
println!("{}", serde_json::json!({"type":"result","content":o}));
}
}