Skip to main content

fur_cli/commands/
rebuild.rs

1use std::path::Path;
2
3use clap::Parser;
4use colored::*;
5
6use crate::schema::rebuild::{detect_state, rebuild, ProjectState, RebuildSummary};
7
8/// Arguments for `fur rebuild`
9#[derive(Parser, Debug)]
10pub struct RebuildArgs {
11    /// Overwrite an existing .fur/ (discards active conversation and cursor state)
12    #[arg(long)]
13    pub force: bool,
14}
15
16/// `fur rebuild` — reconstruct `.fur/` from the documents in `chats/`.
17pub fn run_rebuild(args: RebuildArgs) {
18    let root = Path::new(".");
19
20    match detect_state(root) {
21        ProjectState::Locked => {
22            println!("🔒 Archive is locked. Run `fur unlock` first.");
23            return;
24        }
25        ProjectState::Empty => {
26            println!("📭 No conversation documents found under chats/.");
27            println!("   Run `fur new \"Title\"` to start a diary.");
28            return;
29        }
30        _ => {}
31    }
32
33    match rebuild(root, args.force) {
34        Ok(summary) => report(&summary),
35        Err(e) => eprintln!("❌ {}", e),
36    }
37}
38
39/// Runs before every command. Returns false when the command must not proceed.
40///
41/// viceroy: six commands used to print "🚨 .fur/ not found. Run `fur new`
42/// first." That message is wrong twice over on a copied or locked archive — it
43/// tells someone holding a full diary to create an empty one, which on a locked
44/// project would drop a fresh `.fur/` beside the ciphertext.
45pub fn preflight(exempt: bool) -> bool {
46    let root = Path::new(".");
47
48    match detect_state(root) {
49        ProjectState::Locked if !exempt => {
50            println!(
51                "{}",
52                "🔒 This project is locked.".bright_yellow().bold()
53            );
54            println!("{}", "   Run `fur unlock` to read it.".bright_black());
55            false
56        }
57        ProjectState::Unindexed if !exempt => {
58            auto_rebuild_if_needed();
59            true
60        }
61        _ => true,
62    }
63}
64
65/// Called once at startup. Rebuilds only when `.fur/` is absent and documents
66/// exist — the copied-archive case. An existing `.fur/` is never touched, so
67/// this can never silently destroy live state.
68pub fn auto_rebuild_if_needed() {
69    let root = Path::new(".");
70
71    if detect_state(root) != ProjectState::Unindexed {
72        return;
73    }
74
75    println!(
76        "{}",
77        "📦 Conversation documents found, but no .fur/ index."
78            .bright_yellow()
79            .bold()
80    );
81    println!("{}", "   Rebuilding from chats/...".bright_black());
82
83    match rebuild(root, false) {
84        Ok(summary) => report(&summary),
85        Err(e) => eprintln!("❌ Rebuild failed: {}", e),
86    }
87}
88
89fn report(summary: &RebuildSummary) {
90    println!(
91        "{}",
92        format!(
93            "✔ Rebuilt {} conversation(s), {} message(s).",
94            summary.conversations, summary.messages
95        )
96        .bright_green()
97        .bold()
98    );
99
100    if !summary.skipped.is_empty() {
101        println!("\n{}", "Skipped documents".bold().truecolor(255, 105, 180));
102        for s in &summary.skipped {
103            println!("  ✖ {}", s.truecolor(255, 105, 180));
104        }
105    }
106
107    if !summary.avatars.is_empty() {
108        println!(
109            "\n{} {}",
110            "Avatars found:".bright_cyan().bold(),
111            summary.avatars.join(", ").bright_white().bold()
112        );
113    }
114
115    // `main` and the emoji mapping are reader preferences that no document can
116    // carry, so the guess is stated loudly rather than dimmed into the noise —
117    // a wrong `main` silently misattributes every future bare `fur jot`.
118    if let Some(main) = &summary.guessed_main {
119        println!(
120            "{}",
121            format!("  Assuming \"{}\" is you.", main)
122                .bright_yellow()
123                .bold()
124        );
125    }
126
127    println!(
128        "{}",
129        "  → Run `fur onboard` to confirm that and pick faces."
130            .bright_magenta()
131            .bold()
132    );
133}