flk 0.6.2

A CLI tool for managing flake.nix devShell environments
Documentation
//! # flk - A CLI for managing Nix flake development environments
//!
//! `flk` provides a user-friendly command-line interface for managing Nix flake-based
//! development environments. It abstracts away the complexity of manually editing
//! `flake.nix` files, offering simple commands for common tasks like adding packages,
//! managing environment variables, and creating custom shell commands.
//!
//! ## Architecture
//!
//! This binary crate is the CLI entry point. It uses [clap] for argument parsing and
//! dispatches to handler functions in the [`commands`] module. The core library
//! functionality (flake parsing, generation, utilities) lives in the `flk` library crate.
//!
//! ## Subcommands
//!
//! - `init` - Initialize a new flake environment with language-specific templates
//! - `add`/`remove` - Manage packages in the development environment
//! - `search`/`deep-search` - Search nixpkgs for available packages
//! - `command` - Manage custom shell commands
//! - `env` - Manage environment variables
//! - `lock` - Manage flake.lock backups and restoration
//! - `activate` - Enter the development shell
//! - `export` - Export configuration to Docker, Podman, or JSON
//! - `hook` - Generate shell integration hooks
//! - `direnv` - Manage direnv integration

use anyhow::Result;
use clap::{Parser, Subcommand};

mod commands;
mod nix;

use crate::commands::{
    activate, add, command, completions, direnv, env,
    export::{self, ExportType},
    hook::{self, HookShell},
    init, list, lock, profiles, remove, search, show, update,
};

#[derive(Parser)]
#[command(name = "flk")]
#[command(author = "AEduardo-dev")]
#[command(version)]
#[command(about = "A CLI tool for managing flake.nix devShell environments", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Initialize a new flake.nix in the current directory
    Init {
        /// Project type (rust, python, node, go, or generic)
        #[arg(short, long)]
        template: Option<String>,

        /// Force overwrite if flake.nix already exists
        #[arg(short, long)]
        force: bool,
    },

    /// Search for packages in nixpkgs
    Search {
        /// Package name to search for
        query: String,

        /// Limit number of results
        #[arg(short, long, default_value_t = 10)]
        limit: usize,
    },

    /// Get detailed information about a specific package
    DeepSearch {
        /// Full package name
        package: String,
    },

    /// List the packages of the flake.nix
    List {
        /// Target profile to list packages from
        #[arg(short = 'p', long)]
        profile: Option<String>,
    },
    /// Show flake.nix content in pretty print format
    Show {},

    /// Add a package to the flake.nix
    Add {
        /// Package name to add
        package: String,

        /// Pin to a specific version
        #[arg(short, long)]
        version: Option<String>,

        /// Target profile to add the package to
        #[arg(short = 'p', long)]
        profile: Option<String>,
    },

    /// Remove a package from the flake.nix
    Remove {
        package: String,
        /// Target profile to remove the package from
        #[arg(short = 'p', long)]
        profile: Option<String>,
    },

    /// Update packages to latest version
    /// TODO: manage version pinning after implementing #7
    Update {
        /// Specific packages to update
        packages: Vec<String>,

        /// Show what would be updated without actually updating
        #[arg(short, long)]
        show: bool,
    },

    /// Manage custom commands in the dev shell
    Command {
        #[command(subcommand)]
        action: CommandAction,
        /// Target profile to manage
        #[arg(short = 'p', long)]
        profile: Option<String>,
    },
    /// Manage environment variables in the dev shell
    Env {
        #[command(subcommand)]
        action: EnvAction,
        /// Target profile to manage
        #[arg(short = 'p', long)]
        profile: Option<String>,
    },

    /// Manage flake.lock file
    Lock {
        #[command(subcommand)]
        action: LockAction,
    },

    /// Generate and install shell completions
    Completions {
        /// Install the completions automatically
        #[arg(long)]
        install: bool,

        /// Manually specify shell (if not auto-detected)
        #[arg(value_enum)]
        shell: Option<clap_complete::shells::Shell>,
    },

    /// Reload the current shell environment
    Activate {
        /// Target profile to activate
        #[arg(short = 'p', long)]
        profile: Option<String>,
    },

    /// Export flake configurations (Docker, JSON, etc.)
    Export {
        #[arg(short, long)]
        format: ExportType,
        /// Target profile to export
        #[arg(short = 'p', long)]
        profile: Option<String>,
    },

    /// Direnv integration
    Direnv {
        #[command(subcommand)]
        action: DirenvAction,
    },

    /// Hook for nix-shell compatibility
    Hook {
        #[arg(value_enum)]
        shell: HookShell,
    },

    /// Manage profiles
    Profile {
        #[command(subcommand)]
        action: ProfileAction,
    },
}

#[derive(Subcommand)]
enum CommandAction {
    /// Add a custom command to the dev shell
    Add {
        /// Command name
        name: String,

        /// Command definition (bash code)
        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
        command: Vec<String>,

        /// Source from a file instead
        #[arg(short, long)]
        file: Option<String>,
    },
    /// Remove a custom command from the dev shell
    Remove {
        /// Command name to remove
        name: String,
    },
    /// List all custom commands
    List,
}

#[derive(Subcommand)]
enum EnvAction {
    /// Add an environment variable
    Add {
        /// Variable name
        name: String,
        /// Variable value
        value: String,
    },
    /// Remove an environment variable
    Remove {
        /// Variable name
        name: String,
    },
    /// List all environment variables
    List,
}
#[derive(Subcommand)]
enum LockAction {
    /// Show detailed lock file information
    Show,

    /// Show lock file backup history
    History,

    /// Restore lock file from a backup
    Restore {
        /// Backup timestamp or identifier (e.g., "2025-01-27_14-30-00" or "latest")
        backup: String,
    },
}
#[derive(Subcommand)]
enum DirenvAction {
    /// Create .envrc with flake activation
    Init,
    /// Add flake activation to existing .envrc
    Attach,
    /// Remove flake activation from .envrc
    Detach,
}
#[derive(Subcommand)]
enum ProfileAction {
    /// Create a new profile
    Add {
        /// Profile name
        name: String,
        /// Template to use (base, rust, python, node, go, generic)
        #[arg(short, long)]
        template: Option<String>,
        /// Force overwrite if profile already exists
        #[arg(short, long)]
        force: bool,
    },
    /// Remove an existing profile
    Remove {
        /// Profile name
        name: String,
    },
    /// List all profiles
    List,
    /// Set default profile
    SetDefault {
        /// Profile name
        profile: String,
    },
}

fn main() -> Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Init { template, force } => {
            init::run(template, force)?;
        }
        Commands::Search { query, limit } => {
            search::run_search(&query, limit)?;
        }
        Commands::DeepSearch { package } => {
            search::run_deep_search(&package)?;
        }
        Commands::List { profile } => {
            list::run_list(profile)?;
        }
        Commands::Show {} => {
            show::run_show()?;
        }
        Commands::Add {
            package,
            version,
            profile,
        } => {
            add::run_add(&package, version, profile)?;
        }
        Commands::Remove { package, profile } => {
            remove::run_remove(&package, profile)?;
        }
        Commands::Update { packages, show } => {
            update::run_update(packages, show)?;
        }
        Commands::Command { action, profile } => match action {
            CommandAction::Add {
                name,
                command,
                file,
            } => {
                let cmd = command.join(" ");
                command::run_add(&name, &cmd, file, profile)?;
            }
            CommandAction::Remove { name } => {
                command::run_remove(&name, profile)?;
            }
            CommandAction::List => {
                command::list(profile)?;
            }
        },
        Commands::Env { action, profile } => match action {
            EnvAction::Add { name, value } => {
                env::add(&name, &value, profile)?;
            }
            EnvAction::Remove { name } => {
                env::remove(&name, profile)?;
            }
            EnvAction::List => {
                env::list(profile)?;
            }
        },
        Commands::Lock { action } => match action {
            LockAction::Show => {
                lock::show()?;
            }
            LockAction::History => {
                lock::history()?;
            }
            LockAction::Restore { backup } => {
                lock::restore(&backup)?;
            }
        },
        Commands::Completions { install, shell } => {
            completions::handle_completions(install, shell)?;
        }
        Commands::Activate { profile } => {
            activate::run_activate(profile)?;
        }
        Commands::Export { format, profile } => {
            export::run_export(&format, profile)?;
        }
        Commands::Direnv { action } => match action {
            DirenvAction::Init => {
                direnv::direnv_init()?;
            }
            DirenvAction::Attach => {
                direnv::direnv_attach()?;
            }
            DirenvAction::Detach => {
                direnv::direnv_detach()?;
            }
        },
        Commands::Hook { shell } => {
            hook::run_hook(shell)?;
        }
        Commands::Profile { action } => match action {
            ProfileAction::Add {
                name,
                template,
                force,
            } => {
                profiles::run_add(name, template, force)?;
            }
            ProfileAction::Remove { name } => {
                profiles::run_remove(name)?;
            }
            ProfileAction::List => {
                profiles::run_list()?;
            }
            ProfileAction::SetDefault { profile } => {
                profiles::run_set_default(profile)?;
            }
        },
    }

    Ok(())
}