pub mod commands;
pub mod context;
pub mod output;
use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(
name = "vaultic",
version,
about,
long_about = "Vaultic is a CLI tool for managing secrets and configuration files \
securely across development teams.\n\n\
It encrypts your sensitive files with age or GPG, syncs them via Git, \
detects missing variables, supports multi-environment inheritance, \
and audits every change.",
after_help = "Getting started:\n \
New project: vaultic init\n \
Join a project: vaultic keys setup → send your public key to admin\n \
Check status: vaultic status\n\n\
More info: https://github.com/SoftDryzz/vaultic"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
#[arg(long, global = true, default_value = "age")]
pub cipher: String,
#[arg(long, global = true)]
pub env: Vec<String>,
#[arg(short, long, global = true)]
pub verbose: bool,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(long, global = true)]
pub config: Option<String>,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(
long_about = "Initialize Vaultic in the current project.\n\n\
Creates the .vaultic/ directory, generates config.toml with defaults, \
creates an empty .env.template, and adds .env to .gitignore.\n\n\
During setup, Vaultic detects existing age and GPG keys and offers \
to generate a new key if none is found.",
after_help = "Examples:\n \
vaultic init # Interactive setup with key detection\n \
vaultic init --cipher gpg # Initialize with GPG as default backend"
)]
Init,
#[command(
long_about = "Encrypt secret files for all authorized recipients.\n\n\
Reads the source file, encrypts it with the public keys of all \
recipients listed in .vaultic/recipients.txt, and saves the \
ciphertext as .vaultic/<env>.env.enc.\n\n\
The original file is NOT modified or deleted. Use --all to \
re-encrypt all environments (useful after adding/removing recipients).",
after_help = "Examples:\n \
vaultic encrypt # Encrypt .env as dev\n \
vaultic encrypt .env --env prod # Encrypt as prod environment\n \
vaultic encrypt --all # Re-encrypt all environments\n \
vaultic encrypt --cipher gpg # Encrypt with GPG backend"
)]
Encrypt {
file: Option<String>,
#[arg(long)]
all: bool,
},
#[command(
long_about = "Decrypt secret files using your private key.\n\n\
Reads the encrypted file from .vaultic/<env>.env.enc and writes \
the plaintext to .env in the working directory.\n\n\
By default, uses the age key at ~/.config/age/keys.txt. \
Use --key to specify a different private key location.",
after_help = "Examples:\n \
vaultic decrypt # Decrypt dev environment\n \
vaultic decrypt --env prod # Decrypt prod environment\n \
vaultic decrypt --key /path/to/key # Use custom private key\n \
vaultic decrypt --cipher gpg # Decrypt with GPG backend"
)]
Decrypt {
file: Option<String>,
#[arg(long)]
key: Option<String>,
},
#[command(
long_about = "Verify your local .env against .env.template.\n\n\
Reports missing variables (in template but not in .env), \
extra variables (in .env but not in template), and \
variables with empty values.",
after_help = "Examples:\n \
vaultic check # Check .env vs .env.template"
)]
Check,
#[command(
long_about = "Compare two secret files or two resolved environments side by side.\n\n\
In file mode, compares two .env files directly.\n\
In environment mode (--env dev --env prod), resolves the full \
inheritance chain for each environment before comparing.",
after_help = "Examples:\n \
vaultic diff .env .env.prod # Compare two files\n \
vaultic diff --env dev --env prod # Compare resolved environments\n \
vaultic diff --env dev --env prod --cipher gpg"
)]
Diff {
file1: Option<String>,
file2: Option<String>,
},
#[command(
long_about = "Generate a resolved .env file by applying environment inheritance.\n\n\
Reads the inheritance chain from .vaultic/config.toml, decrypts \
each layer in memory, and merges them from base to leaf. \
The overlay always wins when keys conflict.",
after_help = "Examples:\n \
vaultic resolve --env dev # Resolve dev (base → dev)\n \
vaultic resolve --env staging # Resolve staging chain\n \
vaultic resolve --env prod --cipher gpg"
)]
Resolve,
#[command(
long_about = "Manage encryption keys and authorized recipients.\n\n\
Recipients are public keys stored in .vaultic/recipients.txt. \
Only recipients can decrypt files encrypted for the project.",
after_help = "Examples:\n \
vaultic keys setup # Generate or import a key\n \
vaultic keys add age1abc...xyz # Add a recipient\n \
vaultic keys list # List all recipients\n \
vaultic keys remove age1abc...xyz # Remove a recipient"
)]
Keys {
#[command(subcommand)]
action: KeysAction,
},
#[command(
long_about = "Show the audit log of all Vaultic operations.\n\n\
Each entry records the timestamp, author (from git config), \
action performed, affected files, and an optional state hash.",
after_help = "Examples:\n \
vaultic log # Show full history\n \
vaultic log --last 10 # Show last 10 entries\n \
vaultic log --author \"Alice\" # Filter by author\n \
vaultic log --since 2026-01-01 # Filter by date"
)]
Log {
#[arg(long)]
author: Option<String>,
#[arg(long)]
since: Option<String>,
#[arg(long)]
last: Option<usize>,
},
#[command(long_about = "Show a full project dashboard.\n\n\
Displays configuration, authorized recipients, encrypted \
environments with file sizes, local state (.env, template, \
gitignore), your key info, and audit log entry count.")]
Status,
#[command(
long_about = "Manage git hooks for secret safety.\n\n\
The pre-commit hook blocks plaintext .env files from being \
committed accidentally. It detects Vaultic-managed hooks via \
marker comments and refuses to overwrite foreign hooks.",
after_help = "Examples:\n \
vaultic hook install # Install pre-commit hook\n \
vaultic hook uninstall # Remove pre-commit hook"
)]
Hook {
#[command(subcommand)]
action: HookAction,
},
}
#[derive(Subcommand, Debug)]
pub enum KeysAction {
#[command(long_about = "Interactive key setup for new users.\n\n\
Options:\n \
1. Generate a new age key (recommended)\n \
2. Import an existing age key from file\n \
3. Use an existing GPG key from the system keyring")]
Setup,
#[command(after_help = "Accepted formats:\n \
age key: age1ql3z7hjy54pw...ac8p\n \
GPG fingerprint: A1B2C3D4E5F6...\n \
GPG email: user@example.com")]
Add {
identity: String,
},
List,
Remove {
identity: String,
},
}
#[derive(Subcommand, Debug)]
pub enum HookAction {
Install,
Uninstall,
}