P0 修复:
- B-260615-37: shell.rs 补 Stdio::piped() 修复 run_command stdout/stderr 恒空
(tokio 默认 inherit 导致 wait_with_output 读不到 pipe)
P1 功能:
- B-260615-36: 路由加 /ideas/:id + Ideas.vue 接 route.params.id + watch
(修复项目详情点「来源灵感」跳空白页)
- F-260615-08: 新增 file_info 工具(元信息: exists/size/lines/binary/dir)
- F-260615-09: 新增 append_file 工具(追加写入, RiskLevel::Medium)
- F-260615-12: 新增 search_files 工具(文件名 glob 搜索,递归,限50条)
+ search_files_recursive 辅助函数 + i18n zh/en 3工具×2命名空间
DRY/重构:
- CR-260615-09: .ai-md 样式5份→全局 src/styles/ai-md.css(75行)
AiChat.vue 删71行重复 + main.ts 引入 + typo修正(.a-md→.ai-md)
- 全量 import 路径统一为 @/ 别名(stores/composables/views ~28文件)
vite.config.ts 加 resolve.alias.{ '@': '/src' }
- i18n 批量改进: store error fallback 11处中文→t() / ToolCard ARG_LABEL
/ useAiSend queue文案 / Dashboard 空态 / Ideas.vue null守卫
- cargo check 0 error / vue-tsc 0 error
77 lines
2.1 KiB
Rust
77 lines
2.1 KiB
Rust
//! Shell 执行器 — 通过 tokio::process 执行 shell 命令
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::process::Stdio;
|
||
|
||
/// Shell 命令执行结果
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ShellResult {
|
||
/// 标准输出
|
||
pub stdout: String,
|
||
/// 标准错误
|
||
pub stderr: String,
|
||
/// 退出码
|
||
pub exit_code: Option<i32>,
|
||
/// 执行耗时(毫秒)
|
||
pub duration_ms: u64,
|
||
}
|
||
|
||
/// Shell 命令执行请求
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ShellRequest {
|
||
/// 要执行的命令
|
||
pub command: String,
|
||
/// 工作目录
|
||
pub working_dir: Option<String>,
|
||
/// 环境变量
|
||
pub env: std::collections::HashMap<String, String>,
|
||
/// 超时时间(秒),None 表示不超时
|
||
pub timeout_secs: Option<u64>,
|
||
}
|
||
|
||
/// 执行 Shell 命令
|
||
///
|
||
/// TODO: 完整实现,支持超时、环境变量、工作目录等
|
||
pub async fn execute(request: ShellRequest) -> anyhow::Result<ShellResult> {
|
||
let start = std::time::Instant::now();
|
||
|
||
let mut cmd = if cfg!(windows) {
|
||
let mut c = tokio::process::Command::new("cmd");
|
||
c.arg("/C").arg(&request.command);
|
||
c.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||
c
|
||
} else {
|
||
let mut c = tokio::process::Command::new("sh");
|
||
c.arg("-c").arg(&request.command);
|
||
c.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||
c
|
||
};
|
||
|
||
if let Some(dir) = &request.working_dir {
|
||
cmd.current_dir(dir);
|
||
}
|
||
|
||
for (key, value) in &request.env {
|
||
cmd.env(key, value);
|
||
}
|
||
|
||
let output = match request.timeout_secs {
|
||
Some(secs) => tokio::time::timeout(
|
||
std::time::Duration::from_secs(secs),
|
||
cmd.output(),
|
||
)
|
||
.await
|
||
.map_err(|_| anyhow::anyhow!("命令执行超时: {}秒", secs))??,
|
||
None => cmd.output().await?,
|
||
};
|
||
|
||
let duration = start.elapsed().as_millis() as u64;
|
||
|
||
Ok(ShellResult {
|
||
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
|
||
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
|
||
exit_code: output.status.code(),
|
||
duration_ms: duration,
|
||
})
|
||
}
|