libnoa 0.1.0

AI-native distributed version control system with per-agent workspace isolation, JSONL append-only logs, snapshot-based history, and full git protocol compatibility
Documentation
use clap::{Parser, Subcommand};
use std::path::PathBuf;

use libnoa::cli;

const VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Parser)]
#[command(name = "noa")]
#[command(about = "AI-native distributed version control system")]
#[command(version = VERSION)]
struct App {
    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    Init {
        #[arg(default_value = ".")]
        path: PathBuf,
        #[arg(long)]
        noa_remote: Option<String>,
    },
    Status,
    Log {
        #[arg(short, long)]
        workspace: Option<String>,
        #[arg(short, long, default_value_t = 20)]
        limit: usize,
    },
    Snapshot {
        #[command(subcommand)]
        cmd: SnapshotSub,
    },
    Workspace {
        #[command(subcommand)]
        cmd: WorkspaceSub,
    },
    Remote {
        #[command(subcommand)]
        cmd: RemoteSub,
    },
    Push {
        #[arg(short, long, default_value = "origin")]
        remote: String,
    },
    Pull {
        #[arg(short, long, default_value = "origin")]
        remote: String,
    },
    Fetch {
        #[arg(short, long, default_value = "origin")]
        remote: String,
    },
    Clone {
        url: String,
        #[arg(short, long, default_value = ".")]
        path: String,
        #[arg(long)]
        svn: bool,
    },
}

#[derive(Subcommand)]
enum SnapshotSub {
    Create {
        #[arg(short, long, default_value = "")]
        message: String,
        #[arg(short, long, default_value = "default")]
        author: String,
    },
    List,
    Diff {
        a: String,
        b: String,
    },
}

#[derive(Subcommand)]
enum WorkspaceSub {
    Create {
        name: String,
        #[arg(short, long)]
        agent: Option<String>,
    },
    Switch {
        name: String,
    },
    List,
    Delete {
        name: String,
    },
    Merge {
        from: String,
    },
}

#[derive(Subcommand)]
enum RemoteSub {
    Add { name: String, url: String },
    Remove { name: String },
    List,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let app = App::parse();

    match app.command {
        None => {}
        Some(Commands::Init { path, noa_remote }) => {
            cli::init::run(&cli::init::InitArgs { path, noa_remote })?;
        }
        Some(Commands::Status) => {
            cli::status::run().await?;
        }
        Some(Commands::Log { workspace, limit }) => {
            cli::log_cmd::run(workspace.as_deref(), limit).await?;
        }
        Some(Commands::Snapshot { cmd }) => match cmd {
            SnapshotSub::Create { message, author } => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::snapshot_cmd::run_create(&repo, &message, &author).await?;
            }
            SnapshotSub::List => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::snapshot_cmd::run_list(&repo).await?;
            }
            SnapshotSub::Diff { a, b } => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::snapshot_cmd::run_diff(&repo, &a, &b).await?;
            }
        },
        Some(Commands::Workspace { cmd }) => match cmd {
            WorkspaceSub::Create { name, agent } => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::workspace_cmd::run_create(&repo, &name, agent.as_deref()).await?;
            }
            WorkspaceSub::Switch { name } => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::workspace_cmd::run_switch(&repo, &name).await?;
            }
            WorkspaceSub::List => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::workspace_cmd::run_list(&repo).await?;
            }
            WorkspaceSub::Delete { name } => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::workspace_cmd::run_delete(&repo, &name).await?;
            }
            WorkspaceSub::Merge { from } => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::workspace_cmd::run_merge(&repo, &from).await?;
            }
        },
        Some(Commands::Remote { cmd }) => match cmd {
            RemoteSub::Add { name, url } => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let mut repo = libnoa::repo::Repository::open(&root)?;
                cli::remote_cmd::run_add(&mut repo, &name, &url).await?;
            }
            RemoteSub::Remove { name } => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let mut repo = libnoa::repo::Repository::open(&root)?;
                cli::remote_cmd::run_remove(&mut repo, &name).await?;
            }
            RemoteSub::List => {
                let root = libnoa::repo::Repository::find(std::path::Path::new("."))?;
                let repo = libnoa::repo::Repository::open(&root)?;
                cli::remote_cmd::run_list(&repo).await?;
            }
        },
        Some(Commands::Push { remote }) => {
            cli::pushpull::run_push(&remote).await?;
        }
        Some(Commands::Pull { remote }) => {
            cli::pushpull::run_pull(&remote).await?;
        }
        Some(Commands::Fetch { remote }) => {
            cli::pushpull::run_fetch(&remote).await?;
        }
        Some(Commands::Clone { url, path, svn }) => {
            if svn {
                cli::pushpull::run_clone_svn(&url, &path).await?;
            } else {
                cli::pushpull::run_clone(&url, &path).await?;
            }
        }
    }

    Ok(())
}