mod branding;
mod command_line;
mod config;
mod configs;
mod context;
mod fsutil;
mod keystore;
mod kit;
mod mcp;
mod projects;
mod secrets;
mod select;
mod style;
mod tokens;
#[cfg(feature = "ui")]
pub mod ui;
mod util;
use std::ffi::OsString;
pub use branding::Branding;
pub use command_line::{
Cli, Command, ConfigCommand, ConfigFormatArg, EnvCommand, Format, KeyCommand, McpCommand,
TokenCommand,
};
pub use context::{
CapturedOutput, Context, ContextBuilder, Output, Prompter, ScriptedPrompter, render_error,
};
#[cfg(not(unix))]
fn disable_core_dumps(_branding: &Branding) {}
#[cfg(unix)]
fn disable_core_dumps(branding: &Branding) {
if let Err(e) = rlimit::setrlimit(rlimit::Resource::CORE, 0, 0) {
eprintln!(
"{}: warning: could not disable core dumps: {e}",
branding.message_prefix
);
}
}
pub fn dispatch(ctx: &mut Context, env: Option<&str>, command: Command) -> anyhow::Result<()> {
match command {
Command::Init {
project,
server,
kit,
} => projects::init(ctx, &project, &server, kit.as_deref()),
Command::Env(EnvCommand::Add { path }) => projects::env_add(ctx, &path),
Command::Env(EnvCommand::Ls { path }) => projects::env_ls(ctx, path.as_deref()),
Command::Env(EnvCommand::Rm { path, recursive }) => projects::env_rm(ctx, &path, recursive),
Command::Env(EnvCommand::Repair { path }) => projects::env_repair(ctx, &path),
Command::Key(KeyCommand::Export { path, out, yes }) => {
projects::key_export(ctx, &path, out.as_deref(), yes)
}
Command::Key(KeyCommand::Import { kit }) => projects::key_import(ctx, &kit),
Command::Recover { kit } => projects::recover(ctx, &kit),
Command::Set { name } => secrets::set(ctx, env, &name),
Command::Get { name, version } => secrets::get(ctx, env, &name, version),
Command::Ls { all } => secrets::ls(ctx, env, all),
Command::History { name } => secrets::history(ctx, env, &name),
Command::Rm { name } => secrets::rm(ctx, env, &name),
Command::Config(command) => match command {
ConfigCommand::Set {
name,
format,
file,
allow_literals,
} => configs::set(
ctx,
env,
&name,
format.into(),
file.as_deref(),
allow_literals,
),
ConfigCommand::Get { name, version } => configs::get(ctx, env, &name, version),
ConfigCommand::Ls { all } => configs::ls(ctx, env, all),
ConfigCommand::History { name } => configs::history(ctx, env, &name),
ConfigCommand::Rm { name } => configs::rm(ctx, env, &name),
},
Command::Run { only, command } => secrets::run(ctx, env, &only, &command),
Command::Export {
format,
out,
force,
only,
} => secrets::export(ctx, env, format, out.as_deref(), force, &only),
Command::Token(TokenCommand::Mint { scope, ttl, only }) => {
tokens::mint(ctx, env, &scope, ttl.as_deref(), &only)
}
Command::Token(TokenCommand::Ls) => tokens::ls(ctx, env),
Command::Token(TokenCommand::Revoke { id, rotate }) => {
tokens::revoke(ctx, env, &id, rotate)
}
Command::Token(TokenCommand::Report { file, server }) => {
tokens::report(ctx, file.as_deref(), server.as_deref())
}
Command::Rotate => tokens::rotate(ctx, env),
Command::Rekey {
path,
kit,
resume,
abort,
} => tokens::rekey(ctx, env, path.as_deref(), kit.as_deref(), resume, abort),
Command::Audit => secrets::audit(ctx, env),
Command::Status => secrets::status(ctx, env),
Command::Mcp {
command: Some(McpCommand::Setup { project, ttl }),
..
} => mcp::setup(ctx, &project, ttl.as_deref()),
Command::Mcp {
command: None,
config,
} => mcp::exec(ctx, config.as_deref()),
Command::Ui { no_open } => ui_command(ctx, !no_open),
}
}
#[cfg(feature = "ui")]
fn ui_command(ctx: &mut Context, open_browser: bool) -> anyhow::Result<()> {
ui::run_cli(ctx, open_browser)
}
#[cfg(not(feature = "ui"))]
fn ui_command(ctx: &mut Context, _open_browser: bool) -> anyhow::Result<()> {
let bin = ctx.branding().bin_name;
anyhow::bail!("this {bin} was built without the `ui` feature, so {bin} ui is not available")
}
pub fn run_with<I, T>(branding: &Branding, args: I) -> u8
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
disable_core_dumps(branding);
let cli = Cli::parse_with(branding, args);
let mut ctx = Context::new(*branding);
match dispatch(&mut ctx, cli.env.as_deref(), cli.command) {
Ok(()) => 0,
Err(e) => {
let paint = style::Paint::for_terminal(ctx.output().stderr_is_terminal());
let _ = ctx.output().stderr(
format!(
"{} {}\n",
paint.apply(style::Role::Muted, &format!("{}:", branding.message_prefix)),
paint.apply(style::Role::Warn, &render_error(&e, branding))
)
.as_bytes(),
);
1
}
}
}
pub fn run<I, T>(args: I) -> u8
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
run_with(&Branding::GV, args)
}
#[cfg(all(test, not(feature = "ui")))]
mod tests {
use std::sync::Arc;
use super::*;
#[test]
fn gv_ui_without_the_feature_names_it() {
let dir = tempfile::tempdir().unwrap();
let mut ctx = Context::builder(Branding::GV)
.home(dir.path())
.output(Arc::new(CapturedOutput::new()))
.build();
let e = dispatch(&mut ctx, None, Command::Ui { no_open: true }).unwrap_err();
assert!(e.to_string().contains("`ui` feature"), "{e}");
}
}