pub mod capabilities;
pub mod commands;
pub mod config;
pub mod dependency;
pub mod models;
pub mod registry;
pub mod utils;
pub mod providers;
use clap::{Parser, Subcommand};
use commands::{CapabilityCommands, HardwareCommands, ModelCommands, ProviderCommands};
use utils::ui::{UI_REGISTRY, Ui, run_interactive_tui};
extern crate paste;
#[derive(Parser, Debug)]
#[command(name = "granite-cli")]
#[command(about = "Universal Model Adapter with Capabilities", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(clap::Args, Debug)]
struct ModelWithOutput {
#[arg(short, long, global = true, default_value = "terminal")]
output: String,
#[command(subcommand)]
subcommand: ModelSubcommands,
}
#[derive(clap::Args, Debug)]
struct CapabilityWithOutput {
#[arg(short, long, global = true, default_value = "terminal")]
output: String,
#[command(subcommand)]
subcommand: CapabilitySubcommands,
}
#[derive(clap::Args, Debug)]
struct ProviderWithOutput {
#[arg(short, long, global = true, default_value = "terminal")]
output: String,
#[command(subcommand)]
subcommand: ProviderSubcommands,
}
#[derive(clap::Args, Debug)]
struct ConfigureWithOutput {
#[arg(short, long, global = true, default_value = "terminal")]
output: String,
#[command(flatten)]
args: ConfigureArgs,
}
#[derive(clap::Args, Debug)]
struct LaunchWithOutput {
#[arg(short, long, global = true, default_value = "terminal")]
output: String,
tool_id: String,
#[arg(long)]
dry_run: bool,
#[arg(trailing_var_arg = true)]
args: Vec<String>,
}
#[derive(Subcommand, Debug)]
enum Commands {
Model(ModelWithOutput),
Capability(CapabilityWithOutput),
Provider(ProviderWithOutput),
Hardware,
Configure(ConfigureWithOutput),
Launch(LaunchWithOutput),
}
#[derive(Subcommand, Debug)]
enum ModelSubcommands {
Catalog {
#[arg(short, long)]
r#type: Option<String>,
},
List {
#[arg(short, long)]
r#type: Option<String>,
},
Search {
query: String,
},
Recommend {
#[arg(short, long)]
r#type: Option<String>,
#[arg(short = 'p', long = "providers", value_delimiter = ',')]
providers: Vec<String>,
#[arg(long)]
wide: bool,
},
Info {
model_id: String,
},
Setup {
model_id: String,
},
Pull {
model_id: String,
},
}
#[derive(Subcommand, Debug)]
enum CapabilitySubcommands {
Catalog,
List,
Info {
capability_id: String,
},
Setup {
capability_id: String,
},
}
#[derive(Subcommand, Debug)]
enum ProviderSubcommands {
Catalog,
List,
Setup {
provider_type: String,
#[arg(long = "id")]
instance_id: Option<String>,
},
Health {
provider_id: Option<String>,
},
}
#[derive(clap::Args, Debug)]
struct ConfigureArgs {
tool_id: String,
#[arg(long)]
export: bool,
#[arg(long)]
reset: bool,
}
pub struct AppContext {
pub config: config::Config,
pub ui: Box<dyn Ui>,
}
fn construct_ui(output: &str) -> Box<dyn Ui> {
UI_REGISTRY
.construct(output, &serde_json::json!({}))
.unwrap_or_else(|_| {
eprintln!("Unknown output format '{output}'. Valid: terminal, plain, json, markdown");
std::process::exit(1);
})
}
fn construct_context(output: &str) -> AppContext {
let ui = construct_ui(output);
let config = config::Config::new().unwrap_or_else(|e| {
ui.error(&format!("Failed to load config: {e}"));
std::process::exit(1);
});
AppContext { config, ui }
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let result: Result<(), ()> = match cli.command {
Some(Commands::Model(wrapper)) => {
let mut ctx = construct_context(&wrapper.output);
run_model_command(&mut ctx, wrapper.subcommand)
.await
.map_err(|e| ctx.ui.error(&e.to_string()))
}
Some(Commands::Capability(wrapper)) => {
let mut ctx = construct_context(&wrapper.output);
run_capability_command(&mut ctx, wrapper.subcommand)
.await
.map_err(|e| ctx.ui.error(&e.to_string()))
}
Some(Commands::Provider(wrapper)) => {
let mut ctx = construct_context(&wrapper.output);
run_provider_command(&mut ctx, wrapper.subcommand)
.await
.map_err(|e| ctx.ui.error(&e.to_string()))
}
Some(Commands::Hardware) => {
let ctx = construct_context("terminal");
HardwareCommands::show(&ctx).map_err(|e| ctx.ui.error(&e.to_string()))
}
Some(Commands::Configure(wrapper)) => {
let ui = construct_ui(&wrapper.output);
run_configure(&*ui, wrapper.args)
.await
.map_err(|e| ui.error(&e.to_string()))
}
Some(Commands::Launch(wrapper)) => {
let ui = construct_ui(&wrapper.output);
ui.info("Tool launching will be available in Phase 3.");
Ok(())
}
None => {
let ctx = construct_context("terminal");
run_interactive_tui(ctx)
.await
.map_err(|e| eprintln!("Error: {e}"))
}
};
if result.is_err() {
std::process::exit(1);
}
}
async fn run_model_command(ctx: &mut AppContext, subcmd: ModelSubcommands) -> anyhow::Result<()> {
match subcmd {
ModelSubcommands::Catalog { r#type } => {
let filter = match r#type.as_deref() {
Some("text") => Some(models::ModelType::Text),
Some("vision") => Some(models::ModelType::Vision),
Some("speech") => Some(models::ModelType::Speech),
Some("embedding") => Some(models::ModelType::Embedding),
Some(t) => {
anyhow::bail!(
"Unknown model type: {t}. Valid types: text, vision, speech, embedding"
);
}
None => None,
};
ModelCommands::catalog(ctx, filter)
}
ModelSubcommands::List { r#type } => {
let filter = match r#type.as_deref() {
Some("text") => Some(models::ModelType::Text),
Some("vision") => Some(models::ModelType::Vision),
Some("speech") => Some(models::ModelType::Speech),
Some("embedding") => Some(models::ModelType::Embedding),
Some(t) => {
anyhow::bail!(
"Unknown model type: {t}. Valid types: text, vision, speech, embedding"
);
}
None => None,
};
ModelCommands::list(ctx, filter)
}
ModelSubcommands::Search { query } => ModelCommands::search(ctx, &query),
ModelSubcommands::Recommend {
r#type,
providers,
wide,
} => {
let filter = match r#type.as_deref() {
Some("text") => Some(models::ModelType::Text),
Some("vision") => Some(models::ModelType::Vision),
Some("speech") => Some(models::ModelType::Speech),
Some("embedding") => Some(models::ModelType::Embedding),
Some(t) => {
anyhow::bail!(
"Unknown model type: {t}. Valid types: text, vision, speech, embedding"
);
}
None => None,
};
ModelCommands::recommend(ctx, filter, &providers, wide)
}
ModelSubcommands::Info { model_id } => ModelCommands::info(ctx, &model_id),
ModelSubcommands::Setup { model_id } => ModelCommands::setup(ctx, &model_id).await,
ModelSubcommands::Pull { model_id } => ModelCommands::pull(ctx, &model_id).await,
}
}
async fn run_capability_command(
ctx: &mut AppContext,
subcmd: CapabilitySubcommands,
) -> anyhow::Result<()> {
match subcmd {
CapabilitySubcommands::Catalog => CapabilityCommands::catalog(ctx),
CapabilitySubcommands::List => CapabilityCommands::list(ctx),
CapabilitySubcommands::Info { capability_id } => {
CapabilityCommands::info(ctx, &capability_id)
}
CapabilitySubcommands::Setup { capability_id } => {
CapabilityCommands::setup(ctx, &capability_id).await
}
}
}
async fn run_provider_command(
ctx: &mut AppContext,
subcmd: ProviderSubcommands,
) -> anyhow::Result<()> {
match subcmd {
ProviderSubcommands::Catalog => ProviderCommands::catalog(ctx),
ProviderSubcommands::List => ProviderCommands::list(ctx),
ProviderSubcommands::Setup {
provider_type,
instance_id,
} => ProviderCommands::setup(ctx, &provider_type, instance_id.as_deref()).await,
ProviderSubcommands::Health { provider_id } => {
ProviderCommands::health(ctx, provider_id.as_deref()).await
}
}
}
async fn run_configure(ui: &dyn Ui, _args: ConfigureArgs) -> anyhow::Result<()> {
ui.info("Tool configuration wizard will be available in Phase 3.");
Ok(())
}