braid_http_rs 0.1.5

Unified Braid Protocol implementation in Rust, including Braid-HTTP, Antimatter CRDT, and BraidFS.
Documentation
//! # BraidFS CLI
//!
//! This binary is the Rust port of the Node.js BraidFS implementation.
//! It serves as the entry point for the daemon and control commands.
//!
//! ## Mapping to Original JS
//! - **JS Entry**: `index.js` (manual argv parsing)
//! - **Rust Entry**: Uses `clap` for structured subcommands.
//! - **IPC**: Commands `sync`/`unsync` communicate with the running daemon
//!   via the internal API (default port 45678), similar to how the JS CLI
//!   talks to its local server.
//!
//! See `porting_guide.md` in artifacts for detailed architectural comparison.

use braid_http_rs::fs;
use clap::{Parser, Subcommand};
use reqwest::Client;
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "braidfs")]
#[command(about = "BraidFS: Braid your Filesystem with the Web")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Run the BraidFS daemon
    Run {
        #[arg(short, long, default_value = "45678")]
        port: u16,
    },
    /// Sync a URL to a local file
    Sync {
        url: String,
        #[arg(short, long, default_value = "45678")]
        port: u16,
    },
    /// Unsync a URL
    Unsync {
        url: String,
        #[arg(short, long, default_value = "45678")]
        port: u16,
    },
    /// Mount Braid resources as a native drive
    Mount {
        #[arg(short, long, default_value = "2049")]
        nfs_port: u16,
        #[arg(short, long, default_value = "45678")]
        port: u16,
    },
    /// Unmount Braid native drive
    Unmount {
        #[arg(short, long, default_value = "45678")]
        port: u16,
    },
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialize tracing
    tracing_subscriber::fmt::init();

    let cli = Cli::parse();

    match cli.command {
        Commands::Run { port } => {
            println!("Starting BraidFS daemon on port {}", port);
            fs::run_daemon(port).await?;
        }
        Commands::Sync { url, port } => {
            let client = Client::new();
            let api_url = format!("http://127.0.0.1:{}/api/sync", port);
            println!("Syncing URL via {}: {}", api_url, url);

            let res = client
                .put(&api_url)
                .json(&serde_json::json!({ "url": url }))
                .send()
                .await?;

            if res.status().is_success() {
                println!("Successfully scheduled sync for {}", url);
            } else {
                eprintln!("Failed to sync: {}", res.status());
            }
        }
        Commands::Unsync { url, port } => {
            let client = Client::new();
            let api_url = format!("http://127.0.0.1:{}/api/sync", port);
            println!("Unsyncing URL via {}: {}", api_url, url);

            // DELETE request with body is allowed but sometimes tricky.
            // reqwest supports it.
            let res = client
                .delete(&api_url)
                .json(&serde_json::json!({ "url": url }))
                .send()
                .await?;

            if res.status().is_success() {
                println!("Successfully unsynced {}", url);
            } else {
                eprintln!("Failed to unsync: {}", res.status());
            }
        }
        Commands::Mount { nfs_port, port } => {
            let client = Client::new();
            let api_url = format!("http://127.0.0.1:{}/api/mount", port);
            println!("Activating NFS server via {}...", api_url);

            let res = client
                .put(&api_url)
                .json(&serde_json::json!({ "port": nfs_port }))
                .send()
                .await?;

            if res.status().is_success() {
                println!("NFS Server is now active on port {}", nfs_port);
                println!("\nTo mount Braid as a native drive, run the following command:");
                #[cfg(windows)]
                println!("  mount -o anon \\\\127.0.0.1\\braid Z:");
                #[cfg(not(windows))]
                println!("  sudo mount -t nfs -o port={},mountport={},nfsvers=3,nolock,tcp 127.0.0.1:/ /path/to/mount", nfs_port, nfs_port);
            } else {
                eprintln!("Failed to activate NFS: {}", res.status());
            }
        }
        Commands::Unmount { port } => {
            let client = Client::new();
            let api_url = format!("http://127.0.0.1:{}/api/mount", port);
            println!("Deactivating NFS server via {}...", api_url);

            let res = client.delete(&api_url).send().await?;

            if res.status().is_success() {
                println!("NFS Server deactivated.");
                println!("\nRemember to unmount the drive from your OS:");
                #[cfg(windows)]
                println!("  umount Z:");
                #[cfg(not(windows))]
                println!("  sudo umount /path/to/mount");
            } else {
                eprintln!("Failed to deactivate NFS: {}", res.status());
            }
        }
    }

    Ok(())
}