Skip to main content

vw_cli/
lib.rs

1//! # VibeWindow 代理 CLI 主入口模块
2//!
3//! 本模块是 `vibe-agent` 命令行工具的核心入口点,提供以下主要功能:
4//!
5//! - **代理运行**:启动 AI 代理进行任务执行和代码操作
6//! - **网关服务**:启动 HTTP/WebSocket 网关提供 API 访问
7//! - **守护进程**:作为后台服务运行
8//! - **任务管理**:创建、读取和管理项目任务
9//! - **配置管理**:查看和管理系统配置
10//! - **安全控制**:紧急停止(E-stop)和安全策略管理
11//! - **通道管理**:管理多平台消息通道(Telegram、Slack、Discord 等)
12//! - **诊断工具**:系统诊断和健康检查
13//!
14//! ## 使用示例
15//!
16//! ```bash
17//! # 启动交互式代理
18//! vibe-agent agent
19//!
20//! # 启动网关服务
21//! vibe-agent gateway --port 8080
22//!
23//! # 创建新任务
24//! vibe-agent task create --project-dir /path/to/project --prompt "实现新功能"
25//! ```
26//!
27//! ## 架构说明
28//!
29//! 本模块遵循 trait + 工厂架构,通过以下方式扩展功能:
30//! - 在 `cli` 模块中定义命令
31//! - 在 `handlers` 模块中实现命令处理逻辑
32//! - 通过重新导出保持命令枚举的单一真实来源
33
34#![warn(clippy::all, clippy::pedantic)]
35// #![forbid(unsafe_code)]
36#![allow(
37    clippy::assigning_clones,
38    clippy::bool_to_int_with_if,
39    clippy::case_sensitive_file_extension_comparisons,
40    clippy::cast_possible_wrap,
41    clippy::doc_markdown,
42    clippy::field_reassign_with_default,
43    clippy::float_cmp,
44    clippy::implicit_clone,
45    clippy::items_after_statements,
46    clippy::map_unwrap_or,
47    clippy::manual_let_else,
48    clippy::missing_errors_doc,
49    clippy::missing_panics_doc,
50    clippy::large_futures,
51    clippy::module_name_repetitions,
52    clippy::needless_pass_by_value,
53    clippy::needless_raw_string_hashes,
54    clippy::redundant_closure_for_method_calls,
55    clippy::similar_names,
56    clippy::single_match_else,
57    clippy::struct_field_names,
58    clippy::too_many_lines,
59    clippy::uninlined_format_args,
60    clippy::unused_self,
61    clippy::cast_precision_loss,
62    clippy::unnecessary_cast,
63    clippy::unnecessary_lazy_evaluations,
64    clippy::unnecessary_literal_bound,
65    clippy::unnecessary_map_or,
66    clippy::unnecessary_wraps,
67    dead_code
68)]
69
70use anyhow::{Context, Result, bail};
71use clap::Parser;
72use tracing::info;
73use tracing_subscriber::{EnvFilter, fmt};
74
75use vw_agent::channels::ChannelCommands;
76use vw_agent::cron;
77use vw_agent::integrations;
78use vw_agent::provider::provider;
79use vw_agent::{
80    channels, config, daemon, doctor, gateway, memory, observability, security, service, skills,
81};
82use vw_shared::task::{self, SubTask, Task, TaskExecutorBackend, TaskStatus};
83
84use config::Config;
85use config::schema::ChannelsConfigExt;
86use config::schema::ConfigExt;
87
88#[path = "cli.rs"]
89mod cli;
90#[cfg(test)]
91#[path = "cli_tests.rs"]
92mod cli_tests;
93mod handlers;
94
95pub(crate) mod session {
96    pub(crate) use vw_shared::session::ui_types;
97}
98
99pub(crate) mod app {
100    pub(crate) mod agent {
101        pub(crate) use vw_agent::{
102            approval, channels, config, id, memory, observability, project, providers, runtime,
103            security, shell, skills, tools,
104        };
105
106        pub(crate) mod session {
107            pub(crate) use vw_agent::session::{processor, session, title};
108        }
109
110        #[allow(clippy::module_inception)]
111        pub(crate) mod agent {
112            pub(crate) mod loop_ {
113                pub(crate) use vw_agent::agent::loop_::{context, core, instructions, progress};
114
115                pub(crate) mod cli {
116                    pub(crate) use crate::cli::legacy_runtime::{
117                        logo_text_lines, render_execution_indicator,
118                    };
119                    pub(crate) use crate::cli::legacy_runtime::{theme, transcript, tui_utils};
120                }
121            }
122        }
123    }
124}
125
126use cli::{Cli, Commands, ConfigCommands, DoctorCommands, TaskCommands};
127
128/// 重新导出命令枚举,确保二进制模块可以使用 `crate::<CommandEnum>`
129/// 同时保持命令定义的单一真实来源。
130/// 解析并验证温度参数
131///
132/// 温度参数用于控制 AI 模型输出的随机性,取值范围为 0.0 到 2.0:
133/// - 0.0:最确定性,输出最一致
134/// - 1.0:平衡的随机性
135/// - 2.0:最高随机性,输出最多样化
136///
137/// # 参数
138///
139/// * `s` - 温度值的字符串表示
140///
141/// # 返回值
142///
143/// - `Ok(f64)` - 解析成功的温度值(0.0 到 2.0 之间)
144/// - `Err(String)` - 解析失败或值超出范围时的错误信息
145///
146/// # 示例
147///
148/// ```ignore
149/// let temp = parse_temperature("0.7").unwrap(); // 返回 0.7
150/// let err = parse_temperature("3.0"); // 返回错误,超出范围
151/// ```
152fn parse_temperature(s: &str) -> std::result::Result<f64, String> {
153    let t: f64 = s.parse().map_err(|e| format!("{e}"))?;
154    if !(0.0..=2.0).contains(&t) {
155        return Err("temperature must be between 0.0 and 2.0".to_string());
156    }
157    Ok(t)
158}
159
160/// 解析并验证项目目录路径
161///
162/// 将用户提供的项目目录路径转换为规范的绝对路径,并进行以下验证:
163/// 1. 路径不能为空(去除空白后)
164/// 2. 路径必须存在且可访问
165/// 3. 路径必须指向一个目录而非文件
166///
167/// # 参数
168///
169/// * `project_dir` - 用户提供的项目目录路径字符串
170///
171/// # 返回值
172///
173/// - `Ok(String)` - 规范化后的绝对路径字符串
174/// - `Err(anyhow::Error)` - 路径为空、不存在或不是目录时的错误
175///
176/// # 错误
177///
178/// - 路径为空或仅包含空白字符
179/// - 路径无法解析(权限不足或不存在)
180/// - 路径指向的不是目录
181///
182/// # 示例
183///
184/// ```ignore
185/// let path = resolve_project_dir("./my-project").unwrap();
186/// // 返回类似 "/Users/username/projects/my-project" 的绝对路径
187/// ```
188fn resolve_project_dir(project_dir: &str) -> Result<String> {
189    let trimmed = project_dir.trim();
190    if trimmed.is_empty() {
191        bail!("--project-dir cannot be empty");
192    }
193    let path = std::fs::canonicalize(trimmed)
194        .with_context(|| format!("Failed to resolve project directory: {trimmed}"))?;
195    if !path.is_dir() {
196        bail!("project directory is not a folder: {}", path.display());
197    }
198    Ok(path.to_string_lossy().to_string())
199}
200
201/// 提取文本内容中的第一个非空行
202///
203/// 遍历文本的所有行,跳过空白行,返回第一个包含内容的行。
204/// 如果所有行都为空,则返回空字符串。
205///
206/// # 参数
207///
208/// * `content` - 要处理的文本内容
209///
210/// # 返回值
211///
212/// 第一个非空行的字符串,如果不存在则返回空字符串
213///
214/// # 示例
215///
216/// ```ignore
217/// let text = "\n  \n第一行内容\n第二行内容";
218/// let first = first_non_empty_line(text); // 返回 "第一行内容"
219/// ```
220fn first_non_empty_line(content: &str) -> String {
221    content.lines().map(str::trim).find(|line| !line.is_empty()).unwrap_or_default().to_string()
222}
223
224/// 以 JSON 格式打印单个任务信息
225///
226/// 将任务对象序列化为格式化的 JSON 字符串并输出到标准输出。
227/// 使用 `serde_json::to_string_pretty` 进行美化格式化,便于人类阅读。
228///
229/// # 参数
230///
231/// * `task` - 要打印的任务对象引用
232///
233/// # 返回值
234///
235/// - `Ok(())` - 打印成功
236/// - `Err(anyhow::Error)` - JSON 序列化失败
237///
238/// # 示例
239///
240/// ```ignore
241/// let task = Task::new(1);
242/// print_task_json(&task)?; // 输出格式化的 JSON
243/// ```
244fn print_task_json(task: &Task) -> Result<()> {
245    println!("{}", serde_json::to_string_pretty(task)?);
246    Ok(())
247}
248
249/// 以 JSON 格式打印任务列表信息
250///
251/// 将任务对象数组序列化为格式化的 JSON 字符串并输出到标准输出。
252/// 使用 `serde_json::to_string_pretty` 进行美化格式化,便于人类阅读。
253///
254/// # 参数
255///
256/// * `tasks` - 要打印的任务对象数组切片
257///
258/// # 返回值
259///
260/// - `Ok(())` - 打印成功
261/// - `Err(anyhow::Error)` - JSON 序列化失败
262///
263/// # 示例
264///
265/// ```ignore
266/// let tasks = vec![Task::new(1), Task::new(2)];
267/// print_tasks_json(&tasks)?; // 输出格式化的 JSON 数组
268/// ```
269fn print_tasks_json(tasks: &[Task]) -> Result<()> {
270    println!("{}", serde_json::to_string_pretty(tasks)?);
271    Ok(())
272}
273
274/// CLI 主入口函数
275///
276/// 这是 `vibe-agent` 命令行工具的异步主函数,负责:
277/// 1. 初始化 TLS 加密提供者
278/// 2. 解析命令行参数
279/// 3. 设置配置目录(如果指定)
280/// 4. 处理 shell 补全命令(不初始化日志)
281/// 5. 初始化日志系统(根据模式调整输出目标)
282/// 6. 加载并应用配置
283/// 7. 初始化可观测性和安全模块
284/// 8. 路由到具体命令处理器
285///
286/// # 命令路由
287///
288/// 支持的命令包括:
289/// - `agent`: 启动 AI 代理进行任务执行
290/// - `gateway`: 启动 HTTP/WebSocket 网关服务
291/// - `daemon`: 启动后台守护进程
292/// - `status`: 显示系统状态信息
293/// - `estop`: 紧急停止控制
294/// - `security`: 安全策略管理
295/// - `cron`: 定时任务管理
296/// - `providers`: 列出支持的 AI 提供者
297/// - `service`: 系统服务管理
298/// - `doctor`: 系统诊断工具
299/// - `channel`: 消息通道管理
300/// - `integrations`: 集成管理
301/// - `skills`: 技能管理
302/// - `task`: 任务管理
303/// - `memory`: 记忆系统管理
304/// - `config`: 配置管理
305///
306/// # 返回值
307///
308/// - `Ok(())` - 命令执行成功
309/// - `Err(anyhow::Error)` - 命令执行失败
310///
311/// # 错误处理
312///
313/// 函数会在以下情况下返回错误:
314/// - 配置加载失败
315/// - 命令参数无效
316/// - 命令执行过程中的任何错误
317#[allow(clippy::too_many_lines)]
318pub async fn run() -> Result<()> {
319    // 安装 Rustls TLS 的默认加密提供者
320    // 这可以防止当同时有多个加密库可用时出现的错误:
321    // "could not automatically determine the process-level CryptoProvider"
322    if let Err(e) = rustls::crypto::ring::default_provider().install_default() {
323        eprintln!("Warning: Failed to install default crypto provider: {e:?}");
324    }
325
326    // 解析命令行参数
327    let cli = Cli::parse();
328
329    // 设置自定义配置目录(如果通过 --config-dir 指定)
330    if let Some(config_dir) = &cli.config_dir {
331        if config_dir.trim().is_empty() {
332            bail!("--config-dir cannot be empty");
333        }
334        // 注意:这里使用了 unsafe 代码块来设置环境变量
335        // 在多线程环境下可能有竞争条件,但在这里是安全的
336        unsafe {
337            std::env::set_var("VIBEWINDOW_CONFIG_DIR", config_dir);
338        }
339    }
340
341    // 处理 shell 补全命令
342    // 补全命令必须保持仅输出到标准输出,不加载配置或初始化日志
343    // 这可以避免警告/日志行破坏被 source 的补全脚本
344    if let Commands::Completions { shell } = &cli.command {
345        let mut stdout = std::io::stdout().lock();
346        cli::write_shell_completion(*shell, &mut stdout)?;
347        return Ok(());
348    }
349
350    // 检测是否为交互式代理模式(全屏 TUI 模式)
351    let interactive_agent_mode = matches!(&cli.command, Commands::Agent { message: None, .. });
352
353    // 初始化日志系统
354    // 遵循 RUST_LOG 环境变量,默认为 INFO 级别
355    // 在全屏交互 TUI 模式下,日志会被重定向到 sink 以避免终端输出穿透备用屏幕
356    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
357    if interactive_agent_mode {
358        // 交互模式:日志输出到 sink(丢弃),避免干扰 TUI
359        let subscriber = fmt::Subscriber::builder()
360            .with_timer(tracing_subscriber::fmt::time::ChronoLocal::rfc_3339())
361            .with_env_filter(env_filter)
362            .with_writer(std::io::sink)
363            .finish();
364        tracing::subscriber::set_global_default(subscriber)
365            .expect("setting default subscriber failed");
366    } else {
367        // 非交互模式:日志输出到标准错误
368        let subscriber = fmt::Subscriber::builder()
369            .with_timer(tracing_subscriber::fmt::time::ChronoLocal::rfc_3339())
370            .with_env_filter(env_filter)
371            .finish();
372        tracing::subscriber::set_global_default(subscriber)
373            .expect("setting default subscriber failed");
374    }
375
376    // 加载配置(所有其他命令都需要先加载配置)
377    let mut config = Box::pin(Config::load_or_init()).await?;
378    config.apply_env_overrides();
379
380    // 从配置初始化运行时追踪
381    observability::runtime_trace::init_from_config(&config.observability, &config.workspace_dir);
382
383    // 初始化 OTP(一次性密码)安全验证,如果已启用
384    if config.security.otp.enabled {
385        let config_dir =
386            config.config_path.parent().context("Config path must have a parent directory")?;
387        let store = security::SecretStore::new(config_dir, config.secrets.encrypt);
388        let (_validator, enrollment_uri) =
389            security::OtpValidator::from_config(&config.security.otp, config_dir, &store)?;
390        // 如果是新初始化的 OTP,显示注册 URI
391        if let Some(uri) = enrollment_uri {
392            println!("Initialized OTP secret for VibeWindow.");
393            println!("Enrollment URI: {uri}");
394        }
395    }
396
397    // 根据命令类型路由到相应的处理器
398    match cli.command {
399        /* Commands::Onboard { .. } | */ Commands::Completions { .. } => unreachable!(),
400
401        // 处理 Agent 命令:启动 AI 代理
402        Commands::Agent {
403            message,
404            tui_mode,
405            provider,
406            model,
407            temperature,
408            peripheral,
409            autonomy_level,
410            max_actions_per_hour,
411            max_tool_iterations,
412            max_history_messages,
413            compact_context,
414            memory_backend,
415        } => {
416            // 应用命令行参数覆盖配置中的自主级别
417            if let Some(level) = autonomy_level {
418                config.autonomy.level = level;
419            }
420            // 应用每小时最大操作数限制
421            if let Some(n) = max_actions_per_hour {
422                config.autonomy.max_actions_per_hour = n;
423            }
424            // 应用工具迭代次数限制
425            if let Some(n) = max_tool_iterations {
426                config.agent.max_tool_iterations = n;
427            }
428            // 应用历史消息数量限制
429            if let Some(n) = max_history_messages {
430                config.agent.max_history_messages = n;
431            }
432            // 启用上下文压缩模式
433            if compact_context {
434                config.agent.compact_context = true;
435            }
436            // 应用记忆后端配置
437            if let Some(ref backend) = memory_backend {
438                config.memory.backend = backend.clone();
439            }
440            // 启动代理运行
441            Box::pin(crate::cli::run(
442                config,
443                message,
444                provider,
445                model,
446                temperature,
447                peripheral,
448                interactive_agent_mode,
449                tui_mode,
450            ))
451            .await
452            .map(|_| ())
453        }
454
455        // 处理 Gateway 命令:启动 HTTP/WebSocket 网关服务
456        Commands::Gateway { port, host, new_pairing } => {
457            // 如果请求新的配对,清除已配对的令牌
458            if new_pairing {
459                // 从原始配置持久化令牌重置,以便环境派生的覆盖不会写入磁盘
460                let mut persisted_config = Box::pin(Config::load_or_init()).await?;
461                persisted_config.gateway.paired_tokens.clear();
462                persisted_config.save().await?;
463                config.gateway.paired_tokens.clear();
464                info!("🔐 Cleared paired tokens — a fresh pairing code will be generated");
465            }
466            // 确定监听端口(使用参数或配置中的值)
467            let port = port.unwrap_or(config.gateway.port);
468            // 确定监听主机(使用参数或配置中的值)
469            let host = host.unwrap_or_else(|| config.gateway.host.clone());
470            // 显示启动信息
471            if port == 0 {
472                info!("🚀 Starting VibeWindow Gateway on {host} (random port)");
473            } else {
474                info!("🚀 Starting VibeWindow Gateway on {host}:{port}");
475            }
476            // 启动网关服务
477            gateway::run_gateway(&host, port, config).await
478        }
479
480        // 处理 Daemon 命令:启动后台守护进程
481        Commands::Daemon { port, host } => {
482            // 确定监听端口和主机
483            let port = port.unwrap_or(config.gateway.port);
484            let host = host.unwrap_or_else(|| config.gateway.host.clone());
485            // 显示启动信息
486            if port == 0 {
487                info!("💡 Starting VibeWindow Daemon on {host} (random port)");
488            } else {
489                info!("💡 Starting VibeWindow Daemon on {host}:{port}");
490            }
491            // 启动守护进程
492            daemon::run(config, host, port).await
493        }
494
495        // 处理 Status 命令:显示系统状态
496        Commands::Status => {
497            println!("🦀 VibeWindow Status");
498            println!();
499            println!("Version:     {}", env!("CARGO_PKG_VERSION"));
500            println!("Workspace:   {}", config.workspace_dir.display());
501            println!("Config:      {}", config.config_path.display());
502            println!();
503            println!(
504                "🤖 Provider:      {}",
505                config.default_provider.as_deref().unwrap_or("openrouter")
506            );
507            println!(
508                "   Model:         {}",
509                config.default_model.as_deref().unwrap_or("(default)")
510            );
511            println!("📊 Observability:  {}", config.observability.backend);
512            println!(
513                "🧾 Trace storage:  {} ({})",
514                config.observability.runtime_trace_mode, config.observability.runtime_trace_path
515            );
516            println!("🛡️  Autonomy:      {:?}", config.autonomy.level);
517            println!("⚙️  Runtime:       {}", config.runtime.kind);
518            // 获取有效的记忆后端名称
519            let effective_memory_backend = memory::effective_memory_backend_name(
520                &config.memory.backend,
521                Some(&config.storage.provider.config),
522            );
523            println!(
524                "💓 Heartbeat:      {}",
525                if config.heartbeat.enabled {
526                    format!("every {}min", config.heartbeat.interval_minutes)
527                } else {
528                    "disabled".into()
529                }
530            );
531            println!(
532                "💡 Memory:         {} (auto-save: {})",
533                effective_memory_backend,
534                if config.memory.auto_save { "on" } else { "off" }
535            );
536
537            println!();
538            println!("Security:");
539            println!("  Workspace only:    {}", config.autonomy.workspace_only);
540            println!(
541                "  Allowed roots:     {}",
542                if config.autonomy.allowed_roots.is_empty() {
543                    "(none)".to_string()
544                } else {
545                    config.autonomy.allowed_roots.join(", ")
546                }
547            );
548            println!("  Allowed commands:  {}", config.autonomy.allowed_commands.join(", "));
549            println!("  Max actions/hour:  {}", config.autonomy.max_actions_per_hour);
550            println!(
551                "  Max cost/day:      ${:.2}",
552                f64::from(config.autonomy.max_cost_per_day_cents) / 100.0
553            );
554            println!("  OTP enabled:       {}", config.security.otp.enabled);
555            println!("  E-stop enabled:    {}", config.security.estop.enabled);
556            println!();
557            println!("Channels:");
558            println!("  CLI:      ✅ always");
559            // 显示各个通道的配置状态
560            for (channel, configured) in config.channels_config.channels() {
561                println!(
562                    "  {:9} {}",
563                    channel.name(),
564                    if configured { "✅ configured" } else { "❌ not configured" }
565                );
566            }
567
568            Ok(())
569        }
570
571        /*
572        Commands::Update { check, force } => {
573            update::self_update(force, check).await?;
574            Ok(())
575        }
576        */
577        // 处理 E-stop 命令:紧急停止控制
578        Commands::Estop { estop_command, level, domains, tools } => {
579            handlers::estop::handle_estop_command(&config, estop_command, level, domains, tools)
580        }
581
582        // 处理 Security 命令:安全策略管理
583        Commands::Security { security_command } => {
584            handlers::security::handle_security_command(&config, security_command).await
585        }
586
587        // 处理 Cron 命令:定时任务管理
588        Commands::Cron { cron_command } => cron::handle_command(cron_command, &config),
589
590        // 处理 Providers 命令:列出支持的 AI 提供者
591        Commands::Providers => {
592            // 获取所有可用的提供者列表
593            let provider_map = provider::list().await;
594            let mut providers = provider_map.values().collect::<Vec<_>>();
595            // 按提供者 ID 排序
596            providers.sort_by(|a, b| a.id.cmp(&b.id));
597            // 确定当前活动的提供者
598            let current = config
599                .default_provider
600                .as_deref()
601                .unwrap_or("openrouter")
602                .trim()
603                .to_ascii_lowercase();
604            println!("Supported providers ({} total):\n", providers.len());
605            println!("  ID (use in config)  DESCRIPTION");
606            println!("  ─────────────────── ───────────");
607            // 显示每个提供者的信息,标记当前活动的提供者
608            for p in &providers {
609                let is_active = p.id.eq_ignore_ascii_case(&current);
610                let marker = if is_active { " (active)" } else { "" };
611                println!("  {:<19} {}{}", p.id, p.name, marker);
612            }
613            // 显示自定义端点选项
614            println!("\n  custom:<URL>   Any OpenAI-compatible endpoint");
615            println!("  anthropic-custom:<URL>  Any Anthropic-compatible endpoint");
616            Ok(())
617        }
618
619        // 处理 Service 命令:系统服务管理
620        Commands::Service { service_command, service_init } => {
621            // 解析初始化系统类型
622            let init_system = service_init.parse()?;
623            service::handle_command(&service_command, &config, init_system)
624        }
625
626        // 处理 Doctor 命令:系统诊断工具
627        Commands::Doctor { doctor_command } => match doctor_command {
628            // 检查可用的 AI 模型
629            Some(DoctorCommands::Models { provider, use_cache }) => {
630                doctor::run_models(&config, provider.as_deref(), use_cache).await
631            }
632            // 查看追踪记录
633            Some(DoctorCommands::Traces { id, event, contains, limit }) => doctor::run_traces(
634                &config,
635                id.as_deref(),
636                event.as_deref(),
637                contains.as_deref(),
638                limit,
639            ),
640            // 运行完整的系统诊断
641            None => doctor::run(&config),
642        },
643
644        // 处理 Channel 命令:消息通道管理
645        Commands::Channel { channel_command } => match channel_command {
646            // 启动所有配置的通道
647            ChannelCommands::Start => Box::pin(channels::start_channels(config)).await,
648            // 诊断通道配置问题
649            ChannelCommands::Doctor => channels::doctor_channels(config).await,
650            // 处理其他通道命令
651            other => channels::handle_command(other, &config).await,
652        },
653
654        // 处理 Integrations 命令:集成管理
655        Commands::Integrations { integration_command } => {
656            integrations::handle_command(integration_command, &config)
657        }
658
659        // 处理 Skills 命令:技能管理
660        Commands::Skills { skill_command } => skills::handle_command(skill_command, &config),
661
662        // 处理 Task 命令:任务管理
663        Commands::Task { project_dir, task_command } => {
664            // 解析项目目录路径
665            let project_path = resolve_project_dir(&project_dir)?;
666            match task_command {
667                // 创建新任务
668                TaskCommands::Create {
669                    priority,
670                    prompt,
671                    description,
672                    assignee,
673                    model,
674                    executor,
675                    subtasks,
676                } => {
677                    // 清理和准备任务内容
678                    let prompt = prompt.map(|value| value.trim().to_string());
679                    let description = description.map(|value| value.trim().to_string());
680                    let cleaned_subtasks = subtasks
681                        .into_iter()
682                        .map(|s| s.trim().to_string())
683                        .filter(|s| !s.is_empty())
684                        .collect::<Vec<_>>();
685
686                    // 确定任务的种子内容(从 prompt、description 或 subtasks 中获取第一个非空行)
687                    let mut task_seed = first_non_empty_line(prompt.as_deref().unwrap_or_default());
688                    if task_seed.is_empty() {
689                        task_seed =
690                            first_non_empty_line(description.as_deref().unwrap_or_default());
691                    }
692                    if task_seed.is_empty() {
693                        task_seed = cleaned_subtasks.first().cloned().unwrap_or_default();
694                    }
695                    // 确保至少有一种内容来源
696                    if task_seed.is_empty() {
697                        bail!(
698                            "task content is empty; provide at least one of --prompt, --description, or --subtask"
699                        );
700                    }
701
702                    // 创建任务对象并设置各个字段
703                    let mut task = Task::new(priority);
704
705                    if let Some(description) = description {
706                        task.description = description;
707                    }
708                    if let Some(assignee) = assignee {
709                        let assignee = assignee.trim();
710                        if !assignee.is_empty() {
711                            task.assignee = assignee.to_string();
712                        }
713                    }
714                    if let Some(model) = model {
715                        let model = model.trim();
716                        if !model.is_empty() {
717                            task.model = model.to_string();
718                        }
719                    }
720                    // 解析并设置执行器后端
721                    if let Some(executor) = executor {
722                        let parsed = TaskExecutorBackend::from_id(executor.trim())
723                            .with_context(|| {
724                                format!(
725                                    "Invalid --executor value: {} (supported: internal, opencode, claude, codex)",
726                                    executor
727                                )
728                            })?;
729                        task.executor = parsed;
730                    }
731                    // 设置子任务列表
732                    task.subtasks = cleaned_subtasks.into_iter().map(SubTask::new).collect();
733
734                    // 设置任务的提示内容
735                    match prompt {
736                        Some(prompt) if !prompt.is_empty() => {
737                            task.prompt = prompt;
738                        }
739                        _ => {
740                            task.prompt = first_non_empty_line(&task.description);
741                        }
742                    }
743                    // 如果 prompt 仍为空,使用第一个子任务的内容
744                    if task.prompt.is_empty()
745                        && let Some(subtask) = task.subtasks.first()
746                    {
747                        task.prompt = subtask.content.clone();
748                    }
749
750                    // 创建任务并打印结果
751                    let created = task::create_task(&project_path, task).with_context(|| {
752                        format!("Failed to create task in {}", project_path.as_str())
753                    })?;
754                    print_task_json(&created)
755                }
756                // 读取任务
757                TaskCommands::Read { id, status, include_archived, include_deleted, limit } => {
758                    // 如果指定了任务 ID,直接加载该任务
759                    if let Some(task_id) = id {
760                        let task =
761                            task::load_task(&project_path, task_id.trim()).with_context(|| {
762                                format!(
763                                    "Task not found in {} with id {}",
764                                    project_path.as_str(),
765                                    task_id
766                                )
767                            })?;
768                        return print_task_json(&task);
769                    }
770
771                    // 解析状态过滤器
772                    let status_filter = status
773                        .as_deref()
774                        .map(str::trim)
775                        .filter(|value| !value.is_empty())
776                        .map(|value| {
777                            TaskStatus::parse_key(value)
778                                .with_context(|| format!("Invalid --status value: {value}"))
779                        })
780                        .transpose()?;
781
782                    // 加载所有任务并应用过滤器
783                    let mut tasks = task::load_all_tasks(&project_path);
784                    tasks.retain(|task| {
785                        let status_ok = status_filter.is_none_or(|s| task.status == s);
786                        let archived_ok = include_archived || !task.archived;
787                        let deleted_ok = include_deleted || !task.deleted;
788                        status_ok && archived_ok && deleted_ok
789                    });
790                    // 按创建时间(降序)和优先级(升序)排序
791                    tasks.sort_by(|a, b| {
792                        b.created_at_ms
793                            .cmp(&a.created_at_ms)
794                            .then_with(|| a.priority.cmp(&b.priority))
795                    });
796                    // 限制返回的任务数量
797                    if tasks.len() > limit {
798                        tasks.truncate(limit);
799                    }
800                    print_tasks_json(&tasks)
801                }
802            }
803        }
804
805        /*
806        Commands::Migrate { migrate_command } => {
807            migration::handle_command(migrate_command, &config).await
808        }
809        */
810        // 处理 Memory 命令:记忆系统管理
811        Commands::Memory { memory_command } => {
812            memory::cli::handle_command(memory_command, &config).await
813        }
814
815        /*
816        Commands::Auth { auth_command } => handle_auth_command(auth_command, &config).await,
817        */
818        // 处理 Config 命令:配置管理
819        Commands::Config { config_command } => match config_command {
820            // 输出配置的 JSON Schema
821            ConfigCommands::Schema => {
822                let schema = schemars::schema_for!(config::Config);
823                println!(
824                    "{}",
825                    serde_json::to_string_pretty(&schema).expect("failed to serialize JSON Schema")
826                );
827                Ok(())
828            }
829        },
830    }
831}
832#[cfg(test)]
833#[path = "tests.rs"]
834mod tests;