mod api;
mod changelog;
mod commands;
mod config;
mod output;
mod render;
mod update;
use anyhow::{Context, Result};
use clap::{CommandFactory, Parser, Subcommand};
use owo_colors::OwoColorize;
use api::client::ApiClient;
use config::Config;
use output::Format;
use render::theme;
#[derive(Parser)]
#[command(
name = "fundaia",
version,
about = "Proyectos, servicios, variables, despliegues, logs y métricas",
long_about = None,
propagate_version = true
)]
struct Cli {
#[arg(long, short, global = true, env = "FUNDAIA_PROFILE")]
profile: Option<String>,
#[arg(long, global = true, env = "FUNDAIA_URL")]
url: Option<String>,
#[arg(long, global = true, env = "FUNDAIA_TOKEN", hide_env_values = true)]
token: Option<String>,
#[arg(long, global = true)]
no_color: bool,
#[arg(long, global = true)]
json: bool,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Login {
#[arg(long, default_value = "https://infrastructure.fundaia.com")]
url: String,
#[arg(long, default_value = "default")]
as_profile: String,
#[arg(long)]
no_browser: bool,
},
Logout {
#[arg(long)]
profile: Option<String>,
},
Upgrade {
#[arg(long)]
check: bool,
},
Changelog {
#[arg(value_name = "VERSION")]
release: Option<String>,
#[arg(long)]
all: bool,
#[arg(long)]
since: Option<String>,
#[arg(short = 'n', long)]
count: Option<usize>,
#[arg(long)]
history: bool,
},
Whoami,
Completions { shell: clap_complete::Shell },
Status,
Projects,
Project {
#[command(subcommand)]
command: ProjectCommand,
},
Services { project: String },
Service { project: String, service: String },
Canvas {
#[command(subcommand)]
command: CanvasCommand,
},
New {
project: String,
name: String,
#[arg(long)]
repo: Option<String>,
#[arg(long)]
image: Option<String>,
#[arg(long)]
template: Option<String>,
#[arg(long)]
upload: bool,
#[arg(long, default_value = "main")]
branch: String,
#[arg(long = "connect-to", value_delimiter = ',')]
connect_to: Vec<String>,
#[arg(long = "no-deploy")]
no_deploy: bool,
#[arg(long = "setup-token")]
setup_token: Option<String>,
#[arg(long)]
model: Option<String>,
#[arg(long)]
effort: Option<String>,
},
Agent {
project: String,
service: String,
#[arg(long)]
model: Option<String>,
#[arg(long)]
effort: Option<String>,
#[arg(long = "setup-token")]
setup_token: Option<String>,
},
Rename {
project: String,
service: String,
name: String,
},
Templates,
Up {
project: String,
service: String,
#[arg(long, default_value = ".")]
path: String,
#[arg(long = "no-deploy")]
no_deploy: bool,
#[arg(long)]
detach: bool,
},
Uploads { project: String, service: String },
Deploy {
project: String,
service: String,
#[arg(long)]
restart: bool,
#[arg(long)]
detach: bool,
},
Redeploy {
project: String,
service: String,
number: u32,
},
Rollback {
project: String,
service: String,
number: u32,
},
Stop { project: String, service: String },
History {
project: String,
service: String,
#[arg(long, default_value_t = 15)]
limit: usize,
},
#[command(alias = "show")]
Commit {
project: String,
service: String,
commit: Option<String>,
#[arg(long)]
file: Option<String>,
#[arg(long)]
stat: bool,
},
Activity {
project: String,
#[arg(long, default_value_t = 30)]
limit: usize,
},
#[command(alias = "logins")]
SignIns {
#[arg(long, short = 'n', default_value_t = 50)]
limit: usize,
},
Sessions {
#[command(subcommand)]
command: Option<SessionCommand>,
},
Watch {
project: Option<String>,
service: Option<String>,
},
Logs {
project: String,
service: String,
#[arg(long, short)]
follow: bool,
#[arg(long, short = 'n', default_value_t = 200)]
tail: usize,
#[arg(long)]
stream: Option<String>,
#[arg(long)]
step: Option<String>,
#[arg(long)]
deployment: Option<u32>,
#[arg(long, short = 't')]
timestamps: bool,
#[arg(long)]
trace: Option<String>,
},
Incidents {
project: String,
service: String,
#[arg(long)]
decided: bool,
},
Approvals {
project: String,
service: Option<String>,
},
Approve {
id: String,
},
Reject {
id: String,
},
Fix {
id: String,
},
Incident {
id: String,
#[arg(value_parser = ["resolved", "ignored", "open"])]
status: String,
},
Autofix {
project: String,
service: String,
#[arg(long, value_parser = ["off", "diagnose", "pullRequest"])]
mode: Option<String>,
#[arg(long)]
agent: Option<String>,
#[arg(long)]
model: Option<String>,
#[arg(long)]
min_occurrences: Option<u32>,
#[arg(long)]
daily_limit: Option<u32>,
},
Metrics {
project: String,
service: String,
#[arg(long, default_value_t = 60)]
minutes: u32,
},
#[command(alias = "usage")]
Consumption {
project: String,
service: String,
#[arg(long, default_value = "7d")]
range: String,
},
#[command(name = "agent-activity", alias = "prompts")]
AgentActivity {
project: String,
service: String,
#[arg(long, default_value = "24h")]
range: String,
#[arg(long)]
outcome: Option<String>,
},
#[command(alias = "vars")]
Variables {
#[command(subcommand)]
command: VariableCommand,
},
Access {
#[command(subcommand)]
command: AccessCommand,
},
#[command(alias = "backup")]
Backups {
#[command(subcommand)]
command: BackupCommand,
},
Domains {
#[command(subcommand)]
command: DomainCommand,
},
Connect {
project: String,
service: String,
provider: String,
},
Disconnect {
project: String,
service: String,
provider: String,
},
Connectable { project: String, service: String },
Volumes {
#[command(subcommand)]
command: VolumeCommand,
},
Recipe {
#[command(subcommand)]
command: RecipeCommand,
},
Config {
#[command(subcommand)]
command: ConfigCommand,
},
#[command(alias = "offers")]
Capabilities { project: String, service: String },
#[command(alias = "workspaces")]
Workspace {
#[command(subcommand)]
command: WorkspaceCommand,
},
Capacity,
Settings {
project: String,
service: String,
#[arg(long)]
memory: Option<u32>,
#[arg(long)]
replicas: Option<u32>,
#[arg(long)]
root: Option<String>,
#[arg(long = "start-command")]
start_command: Option<String>,
#[arg(long = "health-path")]
health_path: Option<String>,
#[arg(long = "health-probe")]
health_probe: Option<String>,
#[arg(long = "health-timeout")]
health_timeout_seconds: Option<u32>,
#[arg(long = "health-interval")]
health_interval_seconds: Option<u32>,
#[arg(long = "health-grace")]
health_grace_seconds: Option<u32>,
#[arg(long = "health-status")]
health_expected_status: Option<u32>,
#[arg(long = "restart")]
restart_policy: Option<String>,
#[arg(long = "restart-retries")]
restart_max_retries: Option<u32>,
#[arg(long)]
sleep: Option<bool>,
#[arg(long = "auto-deploy")]
auto_deploy: Option<bool>,
#[arg(long = "cache")]
cache_enabled: Option<bool>,
#[arg(long = "cache-ttl")]
cache_ttl_seconds: Option<u32>,
},
Expose {
project: String,
service: String,
#[arg(long)]
off: bool,
},
Encrypt {
project: String,
service: String,
#[arg(long)]
off: bool,
},
Delete {
project: String,
service: String,
#[arg(long)]
confirm: Option<String>,
},
}
#[derive(Subcommand)]
enum WorkspaceCommand {
List,
#[command(alias = "who")]
Members { workspace: Option<String> },
#[command(alias = "saldo")]
Credits { workspace: Option<String> },
Invite {
email: String,
#[arg(long)]
workspace: Option<String>,
#[arg(long)]
admin: bool,
},
Revoke {
invitation_id: String,
#[arg(long)]
workspace: Option<String>,
},
Remove {
user_id: String,
#[arg(long)]
workspace: Option<String>,
},
}
#[derive(Subcommand)]
enum ProjectCommand {
New {
name: String,
#[arg(long)]
description: Option<String>,
#[arg(long)]
workspace: Option<String>,
},
Rename {
project: String,
name: Option<String>,
#[arg(long)]
description: Option<String>,
},
Delete {
project: String,
#[arg(long)]
confirm: Option<String>,
},
Move { project: String, workspace: String },
}
#[derive(Subcommand)]
enum CanvasCommand {
Show {
project: String,
#[arg(long)]
map: bool,
},
Move {
project: String,
service: String,
#[arg(long, allow_negative_numbers = true)]
x: i64,
#[arg(long, allow_negative_numbers = true)]
y: i64,
},
Arrange {
project: String,
#[arg(long, default_value = "layered", value_parser = ["layered", "grid"])]
layout: String,
},
}
#[derive(Subcommand)]
enum VolumeCommand {
List {
project: String,
service: String,
},
Add {
project: String,
service: String,
mount_path: String,
},
Resize {
volume_id: String,
#[arg(long)]
gb: u64,
},
Remove {
volume_id: String,
},
}
#[derive(Subcommand)]
enum RecipeCommand {
Show {
project: String,
service: String,
#[arg(long)]
plain: bool,
},
Edit {
project: String,
service: String,
#[arg(long, default_value = "-")]
file: String,
},
}
#[derive(Subcommand)]
enum ConfigCommand {
Show {
project: String,
service: String,
#[arg(long)]
plain: bool,
},
Edit {
project: String,
service: String,
#[arg(long, default_value = "-")]
file: String,
},
}
#[derive(Subcommand)]
enum VariableCommand {
List {
project: String,
service: String,
#[arg(long)]
reveal: bool,
},
Set {
project: String,
service: String,
key: String,
value: String,
#[arg(long)]
secret: bool,
#[arg(long)]
sealed: bool,
},
Seal {
project: String,
service: String,
key: String,
#[arg(long)]
confirm: Option<String>,
},
Unset {
project: String,
service: String,
key: String,
},
Import {
project: String,
service: String,
file: String,
#[arg(long)]
secret: bool,
},
}
#[derive(Subcommand)]
enum AccessCommand {
Status { project: String, service: String },
Request {
project: String,
service: String,
#[arg(long)]
email: Option<String>,
},
}
#[derive(Subcommand)]
enum BackupCommand {
List { project: String, service: String },
New { project: String, service: String },
Schedule {
project: String,
service: String,
#[arg(value_delimiter = ',')]
schedules: Vec<String>,
},
Restore {
backup_id: String,
#[arg(long)]
confirm: Option<String>,
},
Keep { backup_id: String },
Unkeep { backup_id: String },
Remove { backup_id: String },
}
#[derive(Subcommand)]
enum SessionCommand {
Revoke { session_id: String },
}
#[derive(Subcommand)]
enum DomainCommand {
List {
project: String,
service: String,
},
Add {
project: String,
service: String,
host: Option<String>,
},
Verify {
domain_id: String,
},
Remove {
domain_id: String,
},
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let format = output::chosen(cli.json);
if cli.no_color || format.is_json() {
owo_colors::set_override(false);
theme::silence();
}
if !matches!(cli.command, Command::Completions { .. }) {
changelog::history::record(env!("CARGO_PKG_VERSION"));
}
let update = match cli.command {
Command::Completions { .. } => None,
_ => Some(update::UpdateCheck::start(
env!("CARGO_PKG_VERSION"),
format.is_json(),
)),
};
let outcome = run(cli, format).await;
if let Some(update) = update {
update.finish().await;
}
if let Err(error) = outcome {
if format.is_json() {
output::emit_error(&error, hint_for(&error));
std::process::exit(1);
}
eprintln!();
eprintln!(" {} {error}", theme::CROSS.style(theme::danger()));
for cause in error.chain().skip(1) {
eprintln!(" {}", cause.style(theme::muted()));
}
if let Some(hint) = hint_for(&error) {
eprintln!(" {}", hint.style(theme::muted()));
}
eprintln!();
std::process::exit(1);
}
}
fn hint_for(error: &anyhow::Error) -> Option<&'static str> {
let api = error.downcast_ref::<api::client::ApiError>()?;
match api.code.as_str() {
"unauthorized" => Some("Ejecuta `fundaia login` para volver a entrar."),
"rate_limited" => Some("Demasiadas peticiones seguidas: espera un minuto."),
"conflict" => Some("Ya hay algo con ese nombre, o hay un despliegue en curso."),
_ => None,
}
}
async fn run(cli: Cli, format: Format) -> Result<()> {
match &cli.command {
Command::Login {
url,
as_profile,
no_browser,
} => return commands::login::run(url, as_profile, !no_browser).await,
Command::Logout { profile } => return commands::login::logout(profile.as_deref()),
Command::Upgrade { check } => {
return update::upgrade(env!("CARGO_PKG_VERSION"), *check).await
}
Command::Changelog {
release,
all,
since,
count,
history,
} => {
return if *history {
commands::changelog::show_history(format)
} else {
commands::changelog::show(
&commands::changelog::Query {
all: *all,
version: release.clone(),
since: since.clone(),
count: *count,
},
format,
)
}
}
Command::Completions { shell } => {
clap_complete::generate(
*shell,
&mut Cli::command(),
"fundaia",
&mut std::io::stdout(),
);
return Ok(());
}
_ => {}
}
let client = connect(&cli)?;
match cli.command {
Command::Login { .. }
| Command::Logout { .. }
| Command::Upgrade { .. }
| Command::Changelog { .. }
| Command::Completions { .. } => {
unreachable!("handled above")
}
Command::Whoami => {
let identity = client.whoami().await?;
if format.is_json() {
output::emit(&serde_json::json!({
"url": client.base(),
"principal": identity,
}));
return Ok(());
}
match identity {
Some(principal) => println!(
" {} en {}",
principal.display_name.style(theme::strong()),
client.base().style(theme::muted()),
),
None => println!(" No hay sesión."),
}
Ok(())
}
Command::Status => commands::read::status(&client, format).await,
Command::Projects => commands::read::projects(&client, format).await,
Command::Project { command } => match command {
ProjectCommand::New {
name,
description,
workspace,
} => {
commands::write::create_project(
&client,
&name,
description.as_deref(),
workspace.as_deref(),
)
.await
}
ProjectCommand::Rename {
project,
name,
description,
} => {
commands::manage::rename_project(
&client,
&project,
name.as_deref(),
description.as_deref(),
)
.await
}
ProjectCommand::Delete { project, confirm } => {
commands::manage::delete_project(&client, &project, confirm.as_deref()).await
}
ProjectCommand::Move { project, workspace } => {
commands::manage::move_project(&client, &project, &workspace).await
}
},
Command::Services { project } => commands::read::services(&client, &project, format).await,
Command::Service { project, service } => {
commands::read::service(&client, &project, &service, format).await
}
Command::Canvas { command } => match command {
CanvasCommand::Show { project, map } => {
commands::read::canvas(&client, &project, map, format).await
}
CanvasCommand::Move {
project,
service,
x,
y,
} => commands::write::move_on_canvas(&client, &project, &service, x, y, format).await,
CanvasCommand::Arrange { project, layout } => {
commands::write::arrange_canvas(&client, &project, &layout, format).await
}
},
Command::Templates => commands::write::templates(&client, format).await,
Command::New {
project,
name,
repo,
image,
template,
upload,
branch,
connect_to,
no_deploy,
setup_token,
model,
effort,
} => {
commands::write::create_service(
&client,
commands::write::ServiceRequest {
project: &project,
name: &name,
repo: repo.as_deref(),
image: image.as_deref(),
template: template.as_deref(),
upload,
branch: &branch,
connect_to: &connect_to,
no_deploy,
setup_token: setup_token.as_deref(),
model: model.as_deref(),
effort: effort.as_deref(),
},
)
.await
}
Command::Agent {
project,
service,
model,
effort,
setup_token,
} => {
commands::write::agent(
&client,
&project,
&service,
api::models::AgentSetupPatch {
model,
effort,
setup_token,
},
format,
)
.await
}
Command::Rename {
project,
service,
name,
} => commands::manage::rename_service(&client, &project, &service, &name).await,
Command::Up {
project,
service,
path,
no_deploy,
detach,
} => {
commands::upload::run(
&client,
&project,
&service,
std::path::Path::new(&path),
!no_deploy,
!detach,
format,
)
.await
}
Command::Uploads { project, service } => {
commands::upload::list(&client, &project, &service, format).await
}
Command::Deploy {
project,
service,
restart,
detach,
} => commands::deploy::run(&client, &project, &service, restart, !detach, format).await,
Command::Redeploy {
project,
service,
number,
} => commands::deploy::redeploy(&client, &project, &service, number, format).await,
Command::Rollback {
project,
service,
number,
} => commands::deploy::rollback(&client, &project, &service, number, format).await,
Command::Stop { project, service } => {
commands::write::stop(&client, &project, &service).await
}
Command::History {
project,
service,
limit,
} => commands::read::history(&client, &project, &service, limit, format).await,
Command::Commit {
project,
service,
commit,
file,
stat,
} => commands::read::commit(&client, &project, &service, commit, file, stat, format).await,
Command::Watch { project, service } => {
commands::watch::run(&client, project.as_deref(), service.as_deref(), format).await
}
Command::Logs {
project,
service,
follow,
tail,
stream,
step,
deployment,
timestamps,
trace,
} => {
commands::logs::run(
&client,
&project,
&service,
commands::logs::LogQuery {
follow,
tail,
stream_filter: stream.as_deref(),
stage: step.as_deref(),
number: deployment,
timestamps,
trace: trace.as_deref(),
},
format,
)
.await
}
Command::Incidents {
project,
service,
decided,
} => commands::read::incidents(&client, &project, &service, decided, format).await,
Command::Approvals { project, service } => {
commands::read::approvals(&client, &project, service.as_deref(), format).await
}
Command::Approve { id } => commands::write::approve_deployment(&client, &id, format).await,
Command::Reject { id } => commands::write::reject_deployment(&client, &id, format).await,
Command::Autofix {
project,
service,
mode,
agent,
model,
min_occurrences,
daily_limit,
} => {
let mut changes = serde_json::Map::new();
if let Some(value) = mode {
changes.insert("mode".into(), value.into());
}
if let Some(value) = agent {
changes.insert("agentServiceEnvironmentId".into(), value.into());
}
if let Some(value) = model {
changes.insert("model".into(), value.into());
}
if let Some(value) = min_occurrences {
changes.insert("minimumOccurrences".into(), value.into());
}
if let Some(value) = daily_limit {
changes.insert("dailyLimit".into(), value.into());
}
commands::read::autofix(
&client,
&project,
&service,
serde_json::Value::Object(changes),
format,
)
.await
}
Command::Fix { id } => commands::write::fix_incident(&client, &id, format).await,
Command::Incident { id, status } => {
commands::write::decide_incident(&client, &id, &status, format).await
}
Command::SignIns { limit } => commands::read::sign_ins(&client, limit, format).await,
Command::Sessions { command } => match command {
None => commands::read::auth_sessions(&client, format).await,
Some(SessionCommand::Revoke { session_id }) => {
client.revoke_session(&session_id).await?;
println!("Sesión cerrada.");
Ok(())
}
},
Command::Activity { project, limit } => {
commands::read::activity(&client, &project, limit, format).await
}
Command::Metrics {
project,
service,
minutes,
} => commands::read::metrics(&client, &project, &service, minutes, format).await,
Command::Consumption {
project,
service,
range,
} => commands::read::consumption(&client, &project, &service, &range, format).await,
Command::AgentActivity {
project,
service,
range,
outcome,
} => {
commands::read::agent_activity(
&client,
&project,
&service,
&range,
outcome.as_deref(),
format,
)
.await
}
Command::Variables { command } => match command {
VariableCommand::List {
project,
service,
reveal,
} => commands::read::variables(&client, &project, &service, reveal, format).await,
VariableCommand::Set {
project,
service,
key,
value,
secret,
sealed,
} => {
commands::write::set_variable(
&client, &project, &service, &key, &value, secret, sealed,
)
.await
}
VariableCommand::Seal {
project,
service,
key,
confirm,
} => {
commands::write::seal_variable(
&client,
&project,
&service,
&key,
confirm.as_deref(),
)
.await
}
VariableCommand::Unset {
project,
service,
key,
} => commands::write::unset_variable(&client, &project, &service, &key).await,
VariableCommand::Import {
project,
service,
file,
secret,
} => {
commands::write::import_variables(&client, &project, &service, &file, secret).await
}
},
Command::Access { command } => match command {
AccessCommand::Status { project, service } => {
commands::read::repository_access(&client, &project, &service, format).await
}
AccessCommand::Request {
project,
service,
email,
} => {
commands::write::request_repository_access(
&client,
&project,
&service,
email.as_deref(),
format,
)
.await
}
},
Command::Backups { command } => match command {
BackupCommand::List { project, service } => {
commands::read::backups(&client, &project, &service, format).await
}
BackupCommand::New { project, service } => {
commands::write::take_backup(&client, &project, &service).await
}
BackupCommand::Schedule {
project,
service,
schedules,
} => commands::write::schedule_backups(&client, &project, &service, &schedules).await,
BackupCommand::Restore { backup_id, confirm } => {
commands::write::restore_backup(&client, &backup_id, confirm.as_deref()).await
}
BackupCommand::Keep { backup_id } => {
commands::write::lock_backup(&client, &backup_id, true).await
}
BackupCommand::Unkeep { backup_id } => {
commands::write::lock_backup(&client, &backup_id, false).await
}
BackupCommand::Remove { backup_id } => {
commands::write::delete_backup(&client, &backup_id).await
}
},
Command::Domains { command } => match command {
DomainCommand::List { project, service } => {
commands::read::domains(&client, &project, &service, format).await
}
DomainCommand::Add {
project,
service,
host,
} => commands::write::add_domain(&client, &project, &service, host.as_deref()).await,
DomainCommand::Verify { domain_id } => {
commands::read::verify_domain(&client, &domain_id, format).await
}
DomainCommand::Remove { domain_id } => {
commands::write::remove_domain(&client, &domain_id).await
}
},
Command::Connect {
project,
service,
provider,
} => commands::write::connect(&client, &project, &service, &provider).await,
Command::Disconnect {
project,
service,
provider,
} => commands::manage::disconnect(&client, &project, &service, &provider).await,
Command::Connectable { project, service } => {
commands::write::candidates(&client, &project, &service, format).await
}
Command::Volumes { command } => match command {
VolumeCommand::List { project, service } => {
commands::read::volumes(&client, &project, &service, format).await
}
VolumeCommand::Add {
project,
service,
mount_path,
} => commands::write::attach_volume(&client, &project, &service, &mount_path).await,
VolumeCommand::Resize { volume_id, gb } => {
commands::write::resize_volume(&client, &volume_id, gb).await
}
VolumeCommand::Remove { volume_id } => {
commands::manage::detach_volume(&client, &volume_id).await
}
},
Command::Recipe { command } => match command {
RecipeCommand::Show {
project,
service,
plain,
} => commands::read::recipe(&client, &project, &service, plain, format).await,
RecipeCommand::Edit {
project,
service,
file,
} => commands::write::edit_recipe(&client, &project, &service, &file).await,
},
Command::Config { command } => match command {
ConfigCommand::Show {
project,
service,
plain,
} => commands::read::service_config(&client, &project, &service, plain, format).await,
ConfigCommand::Edit {
project,
service,
file,
} => commands::write::edit_service_config(&client, &project, &service, &file).await,
},
Command::Capabilities { project, service } => {
commands::read::capabilities(&client, &project, &service, format).await
}
Command::Capacity => commands::manage::capacity(&client, format).await,
Command::Settings {
project,
service,
memory,
replicas,
root,
start_command,
health_path,
health_probe,
health_timeout_seconds,
health_interval_seconds,
health_grace_seconds,
health_expected_status,
restart_policy,
restart_max_retries,
sleep,
auto_deploy,
cache_enabled,
cache_ttl_seconds,
} => {
commands::manage::set_settings(
&client,
&project,
&service,
api::models::SettingsPatch {
memory_mb: memory,
replicas,
root_directory: root,
start_command,
health_path,
health_probe,
health_timeout_seconds,
health_interval_seconds,
health_grace_seconds,
health_expected_status,
restart_policy,
restart_max_retries,
sleep_enabled: sleep,
auto_deploy,
cache_enabled,
cache_ttl_seconds,
},
)
.await
}
Command::Expose {
project,
service,
off,
} => commands::manage::expose(&client, &project, &service, !off).await,
Command::Encrypt {
project,
service,
off,
} => commands::manage::encryption(&client, &project, &service, !off).await,
Command::Delete {
project,
service,
confirm,
} => commands::manage::delete(&client, &project, &service, confirm.as_deref()).await,
Command::Workspace { command } => match command {
WorkspaceCommand::List => commands::workspace::list(&client, format).await,
WorkspaceCommand::Members { workspace } => {
commands::workspace::members(&client, workspace.as_deref(), format).await
}
WorkspaceCommand::Credits { workspace } => {
commands::workspace::credits(&client, workspace.as_deref(), format).await
}
WorkspaceCommand::Invite {
email,
workspace,
admin,
} => commands::workspace::invite(&client, workspace.as_deref(), &email, admin).await,
WorkspaceCommand::Revoke {
invitation_id,
workspace,
} => commands::workspace::revoke(&client, workspace.as_deref(), &invitation_id).await,
WorkspaceCommand::Remove { user_id, workspace } => {
commands::workspace::remove(&client, workspace.as_deref(), &user_id).await
}
},
}
}
fn connect(cli: &Cli) -> Result<ApiClient> {
if let (Some(url), Some(token)) = (&cli.url, &cli.token) {
return ApiClient::new(url, Some(token.clone()));
}
let config = Config::load().context("could not read the configuration")?;
let (_, profile) = config.profile(cli.profile.as_deref())?;
ApiClient::new(
cli.url.as_deref().unwrap_or(&profile.url),
Some(cli.token.clone().unwrap_or_else(|| profile.token.clone())),
)
}