AI CLI 交互设计:当命令行遇上大模型,终端工具的交互革命

cover

一、CLI 工具的学习曲线:为什么新手记不住命令

命令行工具是系统级开发的核心交互方式,但学习曲线陡峭——tar -xzvfffmpeg -i input.mp4 -c:v libx264 -crf 23,这些命令的参数组合让新手望而却步。即使用 --help 查看文档,面对数十个参数也很难快速找到需要的组合。

AI CLI 的核心改进:用自然语言替代命令行参数。用户输入"把当前目录下所有 PNG 转成 WebP,质量 80",AI 自动生成 find . -name "*.png" -exec cwebp -q 80 {} -o {}.webp \;。这不是"替用户写命令",而是"让用户用自然语言表达意图,AI 负责翻译为精确的命令"。

二、AI CLI 的交互架构:从意图理解到命令生成

flowchart TB
    A[用户自然语言输入] --> B[意图解析]
    B --> C[上下文收集]
    C --> D[命令生成]
    D --> E{安全检查}
    E -->|危险操作| F[确认提示]
    E -->|安全操作| G[执行命令]
    F -->|用户确认| G
    F -->|用户拒绝| H[取消]

    G --> I[输出结果]
    I --> J{执行成功?}
    J -->|是| K[完成]
    J -->|否| L[错误分析]
    L --> M[自动修复/建议]
    M --> A

    style B fill:#ff6b6b,color:#fff
    style E fill:#ffd93d,color:#333
    style G fill:#6bcb77,color:#fff

交互流程的关键设计:

  • 意图解析:用 LLM 从自然语言中提取操作意图、目标文件、参数约束。关键是将模糊描述转化为精确的命令参数。
  • 上下文收集:自动收集当前工作目录、文件列表、Git 状态等上下文信息,辅助命令生成。如用户说"编译项目",需要先确认项目类型(Cargo/Make/NPM)。
  • 安全检查:对危险操作(rm -rf、格式化磁盘、覆盖文件)强制要求用户确认。安全检查是 AI CLI 的底线,不能省略。
  • 错误恢复:命令执行失败时,AI 分析错误信息并建议修复方案,而非简单报错退出。

三、AI CLI 工具实现

// AI CLI 核心结构 — Rust 实现
use std::process::Command;
use serde::{Deserialize, Serialize};

/// 用户意图解析结果
#[derive(Debug, Serialize, Deserialize)]
struct ParsedIntent {
    action: String,           // 操作类型: convert/compress/search/compile
    target: Option<String>,   // 目标文件或目录
    params: Vec<String>,      // 参数列表
    dangerous: bool,          // 是否为危险操作
    confidence: f32,          // 解析置信度
}

/// AI CLI 执行器
struct AiCli {
    model_client: ModelClient,
    context: CliContext,
    safety_checker: SafetyChecker,
}

impl AiCli {
    /// 处理用户输入的自然语言命令
    async fn process_input(&self, input: &str) -> Result<Output, CliError> {
        // 第一步:收集上下文
        let context = self.context.collect().await?;

        // 第二步:LLM 解析意图并生成命令
        let prompt = format!(
            "当前目录: {}\n文件列表: {:?}\nGit 分支: {:?}\n\n\
             用户输入: {}\n\n\
             请解析用户意图并生成命令。输出 JSON 格式:\n\
             {{\"command\": \"...\", \"explanation\": \"...\", \
             \"dangerous\": true/false}}",
            context.current_dir,
            context.files.iter().take(20).collect::<Vec<_>>(),
            context.git_branch,
            input
        );

        let response = self.model_client.chat(&prompt).await?;
        let parsed: CommandCandidate = serde_json::from_str(&response)?;

        // 第三步:安全检查
        if parsed.dangerous {
            let confirmed = self.safety_checker.confirm(
                &parsed.command,
                &parsed.explanation,
            )?;
            if !confirmed {
                return Ok(Output::Message("操作已取消".to_string()));
            }
        }

        // 第四步:执行命令
        let result = self.execute_command(&parsed.command).await?;

        // 第五步:如果失败,尝试自动修复
        if !result.success {
            let fix = self.suggest_fix(&parsed.command, &result.stderr).await?;
            return Ok(Output::Suggestion(fix));
        }

        Ok(Output::Result(result))
    }

    /// 执行 shell 命令
    async fn execute_command(&self, cmd: &str) -> Result<CommandResult, CliError> {
        let output = Command::new("sh")
            .arg("-c")
            .arg(cmd)
            .output()?;

        Ok(CommandResult {
            success: output.status.success(),
            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            exit_code: output.status.code().unwrap_or(-1),
        })
    }

    /// 根据错误信息建议修复方案
    async fn suggest_fix(&self, cmd: &str, error: &str) -> Result<String, CliError> {
        let prompt = format!(
            "命令: {}\n错误: {}\n\n请分析错误原因并建议修复命令。",
            cmd, error
        );
        self.model_client.chat(&prompt).await
    }
}

/// 安全检查器
struct SafetyChecker {
    /// 危险命令模式列表
    dangerous_patterns: Vec<&'static str>,
}

impl SafetyChecker {
    fn new() -> Self {
        Self {
            dangerous_patterns: vec![
                "rm -rf",
                "rm -r /",
                "mkfs",
                "dd if=",
                ":(){ :|:& };:",  // fork bomb
                "> /dev/sd",
                "chmod 777",
                "git push --force",
                "git reset --hard",
            ],
        }
    }

    /// 检查命令是否危险,危险则要求用户确认
    fn confirm(&self, command: &str, explanation: &str) -> Result<bool, CliError> {
        let is_dangerous = self.dangerous_patterns.iter()
            .any(|pattern| command.contains(pattern));

        if is_dangerous {
            println!("⚠️  危险操作检测!");
            println!("命令: {}", command);
            println!("说明: {}", explanation);
            println!("确认执行?(y/N): ");

            let mut input = String::new();
            std::io::stdin().read_line(&mut input)?;

            return Ok(input.trim().to_lowercase() == "y");
        }

        Ok(true)
    }
}

/// CLI 上下文收集
struct CliContext;

impl CliContext {
    async fn collect(&self) -> Result<Context, CliError> {
        Ok(Context {
            current_dir: std::env::current_dir()?
                .to_str().unwrap_or("").to_string(),
            files: self.list_files()?,
            git_branch: self.get_git_branch().ok(),
        })
    }

    fn list_files(&self) -> Result<Vec<String>, CliError> {
        let entries: Vec<String> = std::fs::read_dir(".")?
            .filter_map(|e| e.ok())
            .filter_map(|e| e.file_name().to_str().map(String::from))
            .collect();
        Ok(entries)
    }

    fn get_git_branch(&self) -> Result<String, CliError> {
        let output = Command::new("git")
            .args(["branch", "--show-current"])
            .output()?;
        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    }
}

四、AI CLI 的安全边界与交互陷阱

命令注入风险:LLM 生成的命令可能包含用户未预期的操作。如用户说"清理临时文件",AI 可能生成 rm -rf /tmp/*,但 /tmp 下可能有正在使用的文件。安全检查必须覆盖所有文件删除、覆盖和权限修改操作,且默认拒绝而非默认允许。

上下文泄露:AI CLI 需要将当前目录结构、文件内容等发送给 LLM,可能泄露敏感信息(如 .env 文件、密钥文件)。上下文收集必须过滤敏感文件(按 .gitignore 规则排除),且对发送给 LLM 的数据做脱敏处理。

幻觉命令:LLM 可能生成不存在的命令或参数(如 cargo build --release --strip--strip 不是有效参数)。解决方案是在执行前验证命令的合法性(which 检查命令是否存在、--help 检查参数是否有效),或用本地命令文档作为 LLM 的参考上下文。

交互效率的悖论:AI CLI 的目标是简化交互,但如果每次都需要确认、每次都需要等待 LLM 响应(1-3 秒),反而比直接输入命令更慢。解决方案是缓存常见意图的命令映射,高频操作直接匹配本地规则,只有复杂意图才调用 LLM。

五、总结

AI CLI 交互设计的核心原则:自然语言降低门槛、安全检查守住底线、错误恢复提升体验。落地路径:

  1. 起步阶段:实现意图解析和命令生成,覆盖最常用的 20 个命令(文件操作、Git、编译运行),用本地规则 + LLM 混合方案。
  2. 安全加固:建立危险命令模式库,所有删除/覆盖/权限操作强制确认,上下文收集过滤敏感文件。
  3. 体验优化:缓存高频意图的命令映射,减少 LLM 调用;错误时自动建议修复,而非让用户重新描述。
  4. 持续学习:记录用户的命令修改历史(AI 生成的命令被用户手动修改),用反馈数据优化意图解析模型。

AI CLI 不是替代命令行,而是让命令行对更多人可用。好的 AI CLI 应该让新手快速上手,让老手更高效——而不是让两者都变得更慢。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐