mod commands;
mod filters;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "lowfat", version)]
#[command(about = "Token-aware command filter for LLM environments")]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
}
#[derive(Subcommand)]
enum Commands {
Config,
Filters {
#[arg(long)]
commands: bool,
},
Gain,
Level {
value: Option<String>,
},
Status,
Pipeline {
cmd: String,
},
History {
#[arg(long, default_value = "20")]
limit: usize,
#[arg(long)]
all: bool,
#[command(subcommand)]
action: Option<HistoryAction>,
},
Audit {
#[arg(default_value = "20")]
limit: usize,
},
Hook,
ShellInit {
#[arg(default_value = "zsh")]
shell: String,
},
Plugin {
#[command(subcommand)]
action: PluginAction,
},
}
#[derive(Subcommand)]
enum HistoryAction {
Candidates {
#[arg(default_value = "20")]
limit: usize,
#[arg(long)]
all: bool,
},
Export,
#[command(after_help = "\
Examples:
lowfat history prune # default: --older-than 90d
lowfat history prune --older-than 30d # 30d, 2w, 3m suffixes accepted
lowfat history prune --below 2 # drop groups with fewer than 2 runs
lowfat history prune --kept-by-plugin # drop groups already covered by a plugin
lowfat history prune --all # wipe all invocation rows
lowfat history prune --dry-run [...] # preview without deleting")]
Prune {
#[arg(long, value_name = "DURATION")]
older_than: Option<String>,
#[arg(long, value_name = "N")]
below: Option<u64>,
#[arg(long)]
kept_by_plugin: bool,
#[arg(long)]
all: bool,
#[arg(long)]
dry_run: bool,
},
}
#[derive(Subcommand)]
enum PluginAction {
List,
Doctor,
Info { name: String },
Trust { name: String },
Untrust { name: String },
Bench { name: String },
#[command(after_help = "\
Examples:
lowfat plugin new cargo # creates cargo-compact plugin
lowfat plugin new kubectl # creates kubectl-compact plugin
lowfat plugin new eslint -n eslint-filter # custom plugin name")]
New {
command: String,
#[arg(short, long)]
name: Option<String>,
},
}
fn main() {
let cli = Cli::parse();
let result = match cli.command {
Some(Commands::Config) => commands::config::run(),
Some(Commands::Filters { commands }) => commands::filters::run(commands),
Some(Commands::Hook) => commands::hook::run(),
Some(Commands::Gain) => commands::gain::run(),
Some(Commands::Level { value }) => commands::level::run(value.as_deref()),
Some(Commands::Status) => commands::status::run(),
Some(Commands::Pipeline { cmd }) => commands::pipeline::run(&cmd),
Some(Commands::Audit { limit }) => commands::audit::run(limit),
Some(Commands::History { limit, all, action }) => match action {
Some(HistoryAction::Candidates { limit, all }) => commands::candidates::run(limit, all),
Some(HistoryAction::Export) => commands::history_export::run(),
Some(HistoryAction::Prune {
older_than,
below,
kept_by_plugin,
all,
dry_run,
}) => commands::history_prune::run(commands::history_prune::PruneOpts {
older_than,
below,
kept_by_plugin,
all,
dry_run,
}),
None => commands::candidates::run(limit, all),
},
Some(Commands::ShellInit { shell }) => commands::shell_init::run(&shell),
Some(Commands::Plugin { action }) => match action {
PluginAction::List => commands::plugin::list(),
PluginAction::Doctor => commands::plugin::doctor(),
PluginAction::Info { name } => commands::plugin::info(&name),
PluginAction::Trust { name } => commands::plugin::trust(&name),
PluginAction::Untrust { name } => commands::plugin::untrust(&name),
PluginAction::Bench { name } => commands::plugin::bench(&name),
PluginAction::New { command, name } => {
let plugin_name = name.unwrap_or_else(|| format!("{command}-compact"));
commands::plugin::new_plugin(&plugin_name, &command)
}
},
None => {
if cli.args.is_empty() {
commands::help::run();
Ok(())
} else {
let exit_code = commands::run::run(&cli.args);
std::process::exit(exit_code);
}
}
};
if let Err(e) = result {
eprintln!("lowfat: {e}");
std::process::exit(1);
}
}