Skip to main content

codex_sync/cli/
run.rs

1use super::args::{
2    CcSwitchCommand, Cli, Command, HistoryCommand, RelayCommand, SmbCommand, SshCommand, UsbCommand,
3};
4use crate::{cc_switch, config::Config, discovery, history, sync, web};
5use anyhow::Result;
6use clap::Parser;
7
8pub async fn run() -> Result<()> {
9    let cli = Cli::parse();
10    let path = cli.config.unwrap_or_else(Config::default_path);
11    if let Command::Init { force } = cli.command {
12        Config::init(&path, force)?;
13        println!("配置已创建:{}", path.display());
14        return Ok(());
15    }
16
17    if let Command::CcSwitch {
18        command,
19        codex_home,
20        cc_switch_home,
21    } = &cli.command
22    {
23        let paths = cc_switch::Paths::resolve(codex_home.clone(), cc_switch_home.clone())?;
24        match command {
25            CcSwitchCommand::Status => {
26                let report = cc_switch::inspect(&paths)?;
27                cc_switch::print_report(&report);
28            }
29            CcSwitchCommand::Merge { apply } => {
30                let report = cc_switch::merge(&paths, *apply)?;
31                cc_switch::print_report(&report);
32                if *apply {
33                    if let Some(backup) = report.backup {
34                        println!("合并完成;备份:{}", backup.display());
35                    }
36                } else {
37                    println!("以上为预览。关闭 Codex 和 CC-Switch 后,添加 --apply 执行合并。");
38                }
39            }
40        }
41        return Ok(());
42    }
43
44    if let Command::History {
45        command,
46        codex_home,
47    } = &cli.command
48    {
49        let paths = history::Paths::resolve(codex_home.clone())?;
50        match command {
51            HistoryCommand::Status => history::print_report(&history::inspect(&paths)?),
52            HistoryCommand::Doctor {
53                apply,
54                restore_missing,
55                dedupe_session_meta,
56            } => {
57                let report = history::doctor(
58                    &paths,
59                    *apply,
60                    restore_missing,
61                    dedupe_session_meta.as_deref(),
62                )?;
63                history::print_doctor_report(&report);
64                if !*apply {
65                    println!("以上为只读诊断;添加 --apply 可原子修复侧栏索引。");
66                }
67            }
68            HistoryCommand::Merge { apply } => {
69                let report = history::merge(&paths, *apply)?;
70                history::print_report(&report);
71                if *apply {
72                    if let Some(backup) = report.backup {
73                        println!("本地历史合并完成;备份:{}", backup.display());
74                    }
75                } else {
76                    println!("以上为预览;添加 --apply 后才会修改本地历史。");
77                }
78            }
79            HistoryCommand::Backup => {
80                let backup = history::backup(&paths, "manual")?;
81                println!("历史备份已创建:{}", backup.display());
82            }
83            HistoryCommand::Restore { backup, apply } => {
84                if *apply {
85                    let safety = history::restore(&paths, backup)?;
86                    println!("历史已从 {} 恢复", backup.display());
87                    println!("恢复前安全备份:{}", safety.display());
88                } else {
89                    history::validate_backup(backup)?;
90                    println!("将从 {} 恢复;添加 --apply 后才会执行。", backup.display());
91                }
92            }
93        }
94        return Ok(());
95    }
96
97    if let Command::Ssh { command } = &cli.command {
98        match command {
99            SshCommand::Push {
100                host,
101                codex_home,
102                mirror,
103            } => {
104                let report = sync::ssh::push(host, codex_home.clone(), *mirror)?;
105                println!(
106                    "SSH 单向同步完成:新增/更新 {},跳过 {},移除远端独有 {},冲突 {},数据库变更 {},侧栏索引补齐 {}",
107                    report.copied,
108                    report.skipped,
109                    report.removed,
110                    report.conflicts,
111                    report.database_rows,
112                    report.index_entries_added
113                );
114                println!("远端备份:{}", report.backup);
115            }
116            SshCommand::Receive {
117                bundle,
118                codex_home,
119                mirror,
120            } => {
121                let report = sync::ssh::receive(bundle, codex_home.clone(), *mirror)?;
122                println!("CODEX_SYNC_REPORT={}", serde_json::to_string(&report)?);
123            }
124        }
125        return Ok(());
126    }
127
128    let config = Config::load(&path)?;
129    match cli.command {
130        Command::Serve => web::serve(config).await?,
131        Command::Peers { seconds } => {
132            for peer in discovery::discover(&config, seconds).await? {
133                println!("{}\t{}\t{}", peer.name, peer.address, peer.node_id);
134            }
135        }
136        Command::Pull { peer, overwrite } => {
137            let report = sync::http::pull(&config, &peer, overwrite).await?;
138            println!(
139                "下载 {},跳过 {},冲突 {}",
140                report.downloaded, report.skipped, report.conflicts
141            );
142        }
143        Command::Status => {
144            let manifest = sync::core::manifest(&config)?;
145            println!("节点:{}", config.node_name);
146            println!("目录:{}", config.sync_dir.display());
147            println!("文件:{}", manifest.files.len());
148            println!("Web:http://{}", config.listen);
149        }
150        Command::Usb { command } => run_directory_command(&config, command, "快照已导出").await?,
151        Command::Smb { command } => run_smb_command(&config, command).await?,
152        Command::Relay { command } => run_relay_command(&config, command).await?,
153        Command::CcSwitch { .. }
154        | Command::History { .. }
155        | Command::Ssh { .. }
156        | Command::Init { .. } => unreachable!(),
157    }
158    Ok(())
159}
160
161async fn run_directory_command(cfg: &Config, command: UsbCommand, label: &str) -> Result<()> {
162    match command {
163        UsbCommand::Export { destination } => {
164            let path = sync::directory::export(cfg, &destination)?;
165            println!("{label}:{}", path.display());
166        }
167        UsbCommand::Import { source, overwrite } => {
168            print_pull_report(
169                "导入",
170                sync::directory::import(cfg, &source, overwrite).await?,
171            );
172        }
173        UsbCommand::List { source } => print_snapshots(sync::directory::list(&source)?),
174    }
175    Ok(())
176}
177
178async fn run_smb_command(cfg: &Config, command: SmbCommand) -> Result<()> {
179    match command {
180        SmbCommand::Push { share } => {
181            let path = sync::directory::export(cfg, &share)?;
182            println!("快照已推送到 SMB:{}", path.display());
183        }
184        SmbCommand::Pull { share, overwrite } => {
185            print_pull_report(
186                "拉取",
187                sync::directory::import(cfg, &share, overwrite).await?,
188            );
189        }
190        SmbCommand::List { share } => print_snapshots(sync::directory::list(&share)?),
191    }
192    Ok(())
193}
194
195async fn run_relay_command(cfg: &Config, command: RelayCommand) -> Result<()> {
196    match command {
197        RelayCommand::Push { directory } => {
198            let path = sync::directory::export(cfg, &directory)?;
199            println!("快照已发布:{}", path.display());
200        }
201        RelayCommand::Pull {
202            directory,
203            overwrite,
204        } => print_pull_report(
205            "获取",
206            sync::directory::import(cfg, &directory, overwrite).await?,
207        ),
208        RelayCommand::List { directory } => print_snapshots(sync::directory::list(&directory)?),
209    }
210    Ok(())
211}
212
213fn print_pull_report(verb: &str, report: sync::core::PullReport) {
214    println!(
215        "{verb} {},跳过 {},冲突 {}",
216        report.downloaded, report.skipped, report.conflicts
217    );
218}
219
220fn print_snapshots(snapshots: Vec<sync::directory::SnapshotInfo>) {
221    for snapshot in snapshots {
222        println!(
223            "{}\t{}\t{} 个文件\t{}",
224            snapshot.node_name,
225            snapshot.node_id,
226            snapshot.files,
227            snapshot.path.display()
228        );
229    }
230}