devist 0.1.1

Project bootstrap CLI for AI-assisted development. Spin up new projects from templates, manage backends, and keep your codebase comprehensible.
mod commands;
mod config;
mod git;
mod paths;
mod registry;
mod render;
mod runner;
mod scanner;
mod state;
mod template;

use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "devist")]
#[command(version, about = "Project bootstrap CLI for AI-assisted development")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Check environment dependencies
    Doctor,
    /// Show devist info
    About,
    /// Initialize ~/.devist workspace (run once)
    Setup,
    /// Manage templates
    Template {
        #[command(subcommand)]
        cmd: commands::template::TemplateCmd,
    },
    /// Manage registered projects
    Projects {
        #[command(subcommand)]
        cmd: commands::projects::ProjectsCmd,
    },
    /// Create a new project from a template
    Init {
        /// Project name
        name: String,
        /// Template to use
        #[arg(short, long)]
        template: String,
        /// Target directory (defaults to ./<name>)
        #[arg(long)]
        path: Option<PathBuf>,
        /// Override variables: --var key=value
        #[arg(long = "var", value_name = "KEY=VALUE")]
        vars: Vec<String>,
    },
    /// Start a project's backend (stops any other active backend first)
    Start {
        /// Project name
        name: String,
        /// Also run the dev server in foreground after starting backend
        #[arg(long)]
        dev: bool,
    },
    /// Stop the active project's backend (or a specific one)
    Stop {
        /// Optional project name (defaults to currently active)
        name: Option<String>,
    },
    /// Print a project brief (git state + scan + recent activity)
    Brief {
        /// Project name
        name: String,
        /// Output as JSON instead of Markdown
        #[arg(long)]
        json: bool,
    },
    /// Scan a project's codebase metadata
    Scan {
        /// Project name
        name: String,
        /// Output as JSON
        #[arg(long)]
        json: bool,
        /// Print directory tree instead of stats
        #[arg(long)]
        tree: bool,
        /// Max depth for tree output
        #[arg(long, default_value_t = 4)]
        max_depth: usize,
    },
    /// Explain a specific file or directory inside a project
    Explain {
        /// Project name
        name: String,
        /// Path or filename inside the project
        target: String,
        /// Max lines of file content to display
        #[arg(long, default_value_t = 200)]
        max_lines: usize,
    },
    /// Watch a project for changes and print activity
    Watch {
        /// Project name
        name: String,
        /// Debounce in milliseconds
        #[arg(long, default_value_t = 500)]
        debounce_ms: u64,
    },
}

fn main() -> Result<()> {
    let cli = Cli::parse();
    match cli.command {
        Commands::Doctor => commands::doctor::run(),
        Commands::About => commands::about::run(),
        Commands::Setup => commands::init_workspace::run(),
        Commands::Template { cmd } => commands::template::run(cmd),
        Commands::Projects { cmd } => commands::projects::run(cmd),
        Commands::Init {
            name,
            template,
            path,
            vars,
        } => commands::init::run(name, template, path, vars),
        Commands::Start { name, dev } => commands::start::run(name, dev),
        Commands::Stop { name } => commands::stop::run(name),
        Commands::Brief { name, json } => commands::brief::run(name, json),
        Commands::Scan {
            name,
            json,
            tree,
            max_depth,
        } => commands::scan::run(name, json, tree, max_depth),
        Commands::Explain {
            name,
            target,
            max_lines,
        } => commands::explain::run(name, target, max_lines),
        Commands::Watch { name, debounce_ms } => commands::watch::run(name, debounce_ms),
    }
}