use std::path::PathBuf;
use std::process::ExitCode;
use std::time::Duration;
use clap::{Parser, Subcommand, ValueEnum};
use reqwest::Method;
use serde_json::{json, Value};
use sylphx_sdk_core::{RetryConfig, DEFAULT_MANAGEMENT_BASE_URL};
use sylphx_sdk_management::{ListOptions, ManagementClient};
#[derive(Parser, Debug)]
#[command(
name = "sylphx",
about = "Sylphx Platform CLI (Rust) — agent operator surface via sylphx-sdk-management",
version
)]
struct Cli {
#[arg(long, env = "SYLPHX_API_URL", global = true)]
api_url: Option<String>,
#[arg(long, env = "SYLPHX_TOKEN", global = true)]
token: Option<String>,
#[arg(long, global = true, default_value_t = false)]
json: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
Health,
Whoami,
Orgs {
#[command(subcommand)]
command: OrgCommands,
},
Projects {
#[command(subcommand)]
command: ProjectCommands,
},
Environments {
#[command(subcommand)]
command: EnvironmentCommands,
},
Deployments {
#[command(subcommand)]
command: DeploymentCommands,
},
Deploy {
project_id: String,
#[arg(long)]
env: Option<String>,
#[arg(long)]
env_id: Option<String>,
#[arg(long)]
r#ref: Option<String>,
#[arg(long)]
service: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Rollback {
project_id: String,
#[arg(long)]
env_id: Option<String>,
#[arg(long)]
deployment: Option<String>,
#[arg(long)]
service: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Status {
project_id: String,
#[arg(long)]
org_id: Option<String>,
},
Env {
#[command(subcommand)]
command: EnvCommands,
},
Logs {
#[arg(long)]
project: Option<String>,
#[arg(long)]
environment: Option<String>,
#[arg(long = "type", default_value = "runtime")]
log_type: String,
#[arg(long)]
env: Option<String>,
#[arg(long, default_value_t = 100)]
tail: u32,
#[arg(long)]
level: Option<String>,
#[arg(long)]
service: Option<String>,
#[arg(long)]
grep: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Resources {
#[command(subcommand)]
command: ResourceCommands,
},
Db {
#[command(subcommand)]
command: DbCommands,
},
Storage {
#[command(subcommand)]
command: StorageCommands,
},
Tasks {
#[command(subcommand)]
command: TaskCommands,
},
Backup {
#[command(subcommand)]
command: BackupCommands,
},
Flags {
#[command(subcommand)]
command: FlagCommands,
},
Previews {
#[command(subcommand)]
command: PreviewCommands,
},
Api {
method: ApiMethod,
path: String,
#[arg(long)]
body: Option<String>,
#[arg(long)]
input: Option<PathBuf>,
#[arg(long)]
org_id: Option<String>,
},
VersionInfo,
}
#[derive(Subcommand, Debug)]
enum OrgCommands {
List,
}
#[derive(Subcommand, Debug)]
enum ProjectCommands {
List {
#[arg(long)]
org_id: Option<String>,
},
Get {
id: String,
#[arg(long)]
org_id: Option<String>,
},
Create {
name: String,
#[arg(long)]
slug: Option<String>,
#[arg(long)]
org_id: Option<String>,
#[arg(long)]
description: Option<String>,
#[arg(long)]
r#ref: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum EnvironmentCommands {
List {
project_id: String,
#[arg(long)]
org_id: Option<String>,
#[arg(long)]
page_token: Option<String>,
#[arg(long)]
page_size: Option<u32>,
},
Get {
project_id: String,
environment_id: String,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum DeploymentCommands {
List {
project_id: String,
#[arg(long)]
org_id: Option<String>,
#[arg(long)]
page_token: Option<String>,
#[arg(long)]
page_size: Option<u32>,
},
}
#[derive(Subcommand, Debug)]
enum EnvCommands {
List {
project_id: String,
#[arg(long)]
env: Option<String>,
#[arg(long)]
env_id: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Set {
project_id: String,
assignment: String,
#[arg(long, default_value_t = false)]
secret: bool,
#[arg(long)]
env: Option<String>,
#[arg(long)]
env_id: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Rm {
project_id: String,
key: String,
#[arg(long)]
env: Option<String>,
#[arg(long)]
env_id: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum ResourceCommands {
List {
#[arg(long)]
kind: Option<String>,
#[arg(long)]
project: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Get {
id: String,
#[arg(long)]
org_id: Option<String>,
},
Create {
kind: String,
name: String,
#[arg(long)]
project: Option<String>,
#[arg(long)]
tier: Option<String>,
#[arg(long)]
storage: Option<u32>,
#[arg(long)]
org_id: Option<String>,
},
Delete {
id: String,
#[arg(long)]
org_id: Option<String>,
},
Bind {
resource_id: String,
environment_id: String,
#[arg(long)]
project: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum DbCommands {
Create {
name: String,
#[arg(long)]
project: Option<String>,
#[arg(long, default_value = "standard")]
tier: String,
#[arg(long, default_value_t = 10)]
storage: u32,
#[arg(long)]
org_id: Option<String>,
},
List {
#[arg(long)]
project: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Get {
id: String,
#[arg(long)]
org_id: Option<String>,
},
Delete {
id: String,
#[arg(long)]
org_id: Option<String>,
},
Branch {
resource_id: String,
#[arg(long)]
name: String,
#[arg(long)]
env: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum StorageCommands {
Create {
project_id: String,
name: String,
#[arg(long)]
env: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
List {
project_id: String,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum TaskCommands {
List {
#[arg(long)]
project: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Create {
name: String,
#[arg(long)]
project: Option<String>,
#[arg(long)]
mode: Option<String>,
#[arg(long)]
handler: Option<String>,
#[arg(long)]
cron: Option<String>,
#[arg(long)]
callback_url: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Get {
id: String,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum BackupCommands {
List {
#[arg(long)]
resource: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Create {
#[arg(long)]
resource: Option<String>,
#[arg(long)]
tag: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Restore {
backup_id: String,
#[arg(long)]
resource: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum FlagCommands {
List {
#[arg(long)]
project: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
Create {
key: String,
#[arg(long)]
description: Option<String>,
#[arg(long)]
project: Option<String>,
#[arg(long)]
enabled: Option<bool>,
#[arg(long)]
org_id: Option<String>,
},
Toggle {
key: String,
#[arg(long)]
enabled: Option<bool>,
#[arg(long)]
project: Option<String>,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum PreviewCommands {
Cost {
project_id: String,
pr_number: String,
#[arg(long)]
org_id: Option<String>,
},
}
#[derive(Clone, Debug, ValueEnum)]
enum ApiMethod {
Get,
Head,
Post,
Put,
Patch,
Delete,
}
impl ApiMethod {
fn to_reqwest(self) -> Method {
match self {
Self::Get => Method::GET,
Self::Head => Method::HEAD,
Self::Post => Method::POST,
Self::Put => Method::PUT,
Self::Patch => Method::PATCH,
Self::Delete => Method::DELETE,
}
}
}
fn page_opts(page_token: Option<String>, page_size: Option<u32>) -> Option<ListOptions> {
if page_token.is_none() && page_size.is_none() {
return None;
}
Some(ListOptions {
page_token,
page_size,
})
}
const SHIPPED_COMMANDS: &[&str] = &[
"health",
"whoami",
"orgs list",
"projects list",
"projects get",
"projects create",
"environments list",
"environments get",
"deployments list",
"deploy",
"rollback",
"status",
"env list",
"env set",
"env rm",
"logs",
"resources list",
"resources get",
"resources create",
"resources delete",
"resources bind",
"db create",
"db list",
"db get",
"db delete",
"db branch",
"storage create",
"storage list",
"tasks list",
"tasks create",
"tasks get",
"backup list",
"backup create",
"backup restore",
"flags list",
"flags create",
"flags toggle",
"previews cost",
"api",
"version-info",
];
#[tokio::main]
async fn main() -> ExitCode {
match run().await {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("error: {err}");
ExitCode::from(1)
}
}
}
async fn run() -> anyhow::Result<()> {
let cli = Cli::parse();
if matches!(cli.command, Commands::VersionInfo) {
let body = json!({
"cli": env!("CARGO_PKG_VERSION"),
"contractPackage": sylphx_wire::CONTRACT_PACKAGE,
"contractRevision": sylphx_wire::CONTRACT_REVISION,
"client": "sylphx-sdk-management",
"product": "rust",
"shippedCommands": SHIPPED_COMMANDS,
});
print_out(&cli, &body, || {
println!(
"sylphx {} (Rust) contract {}@{} via sylphx-sdk-management",
env!("CARGO_PKG_VERSION"),
sylphx_wire::CONTRACT_PACKAGE,
sylphx_wire::CONTRACT_REVISION
);
});
return Ok(());
}
let token = cli
.token
.clone()
.filter(|t| !t.trim().is_empty())
.ok_or_else(|| anyhow::anyhow!("SYLPHX_TOKEN or --token is required"))?;
let base = cli
.api_url
.clone()
.unwrap_or_else(|| DEFAULT_MANAGEMENT_BASE_URL.to_string());
let client = ManagementClient::builder(token)
.base_url(base)
.retry(RetryConfig {
max_attempts: 3,
base_delay: Duration::from_millis(100),
})
.build()?;
match &cli.command {
Commands::Health => {
let h = client.health().await?;
print_out(&cli, &h, || {
println!(
"status={} version={} git_sha={}",
h.status,
h.version.as_deref().unwrap_or("-"),
h.git_sha.as_deref().unwrap_or("-")
);
});
}
Commands::Whoami => {
let w = client.whoami().await?;
print_out(&cli, &w, || {
let user = w.user.as_ref();
println!(
"user_id={} email={} name={} orgs={}",
user.map(|u| u.id.as_str()).unwrap_or("-"),
user.map(|u| u.email.as_str()).unwrap_or("-"),
user.and_then(|u| u.name.as_deref()).unwrap_or("-"),
w.orgs.len()
);
});
}
Commands::Orgs {
command: OrgCommands::List,
} => {
let orgs = client.list_orgs().await?;
emit_json_or(&cli, &orgs, || {
for o in &orgs {
println!("{}\t{}\t{}", o.id, o.slug, o.name);
}
})?;
}
Commands::Projects {
command: ProjectCommands::List { org_id },
} => {
let list = client.list_projects(org_id.as_deref(), None).await?;
emit_json_or(&cli, &list, || {
for p in &list.data {
println!("{}\t{}\t{}", p.id, p.slug, p.name);
}
})?;
}
Commands::Projects {
command: ProjectCommands::Get { id, org_id },
} => {
let p = client.get_project(&id, org_id.as_deref()).await?;
emit_json_or(&cli, &p, || {
println!(
"id={} slug={} name={} org_id={} active={}",
p.id, p.slug, p.name, p.org_id, p.is_active
);
})?;
}
Commands::Projects {
command:
ProjectCommands::Create {
name,
slug,
org_id,
description,
r#ref,
},
} => {
let p = client
.create_project(
&name,
slug.as_deref(),
org_id.as_deref(),
description.as_deref(),
r#ref.as_deref(),
)
.await?;
emit_json_or(&cli, &p, || {
println!("created id={} slug={} name={}", p.id, p.slug, p.name);
})?;
}
Commands::Environments {
command:
EnvironmentCommands::List {
project_id,
org_id,
page_token,
page_size,
},
} => {
let page = page_opts(page_token.clone(), page_size.as_ref().copied());
let list = client
.list_environments(&project_id, page.as_ref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &list, || {
for e in &list.data {
println!(
"{}\t{}\t{}\t{}",
e.id,
e.slug,
e.name,
e.status.as_deref().unwrap_or("-")
);
}
})?;
}
Commands::Environments {
command:
EnvironmentCommands::Get {
project_id,
environment_id,
org_id,
},
} => {
let e = client
.get_environment(&project_id, &environment_id, org_id.as_deref())
.await?;
emit_json_or(&cli, &e, || {
println!(
"id={} slug={} status={}",
e.id,
e.slug,
e.status.as_deref().unwrap_or("-")
);
})?;
}
Commands::Deployments {
command:
DeploymentCommands::List {
project_id,
org_id,
page_token,
page_size,
},
} => {
let page = page_opts(page_token.clone(), page_size.as_ref().copied());
let list = client
.list_deployments(&project_id, page.as_ref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &list, || {
for d in &list.data {
println!(
"{}\t{}\t{}",
d.id,
d.status,
d.git_sha.as_deref().unwrap_or("-")
);
}
})?;
}
Commands::Deploy {
project_id,
env,
env_id,
r#ref,
service,
org_id,
} => {
let r = client
.create_deploy(
&project_id,
env.as_deref(),
env_id.as_deref(),
r#ref.as_deref(),
service.as_deref(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
println!("deploy id={} status={}", r.id, r.status);
})?;
}
Commands::Rollback {
project_id,
env_id,
deployment,
service,
org_id,
} => {
let r = client
.rollback(
&project_id,
env_id.as_deref(),
deployment.as_deref(),
service.as_deref(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
println!("rollback id={} status={}", r.id, r.status);
})?;
}
Commands::Status {
project_id,
org_id,
} => {
let s = client
.get_project_status(&project_id, org_id.as_deref())
.await?;
emit_json_or(&cli, &s, || {
println!(
"project={} status={} deployment={}",
s.project_id,
s.status,
s.deployment_id.as_deref().unwrap_or("-")
);
})?;
}
Commands::Env {
command:
EnvCommands::List {
project_id,
env,
env_id,
org_id,
},
} => {
let list = client
.list_env_vars(
&project_id,
env.as_deref(),
env_id.as_deref(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &list, || {
for e in &list.data {
println!(
"{}\tsecret={}\t{}",
e.key,
e.secret,
e.value.as_deref().unwrap_or("***")
);
}
})?;
}
Commands::Env {
command:
EnvCommands::Set {
project_id,
assignment,
secret,
env,
env_id,
org_id,
},
} => {
let (key, value) = assignment
.split_once('=')
.ok_or_else(|| anyhow::anyhow!("env set requires KEY=value"))?;
let r = client
.set_env_var(
&project_id,
key,
value,
*secret,
env.as_deref(),
env_id.as_deref(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
println!("set key={} created={}", key, r.created);
})?;
}
Commands::Env {
command:
EnvCommands::Rm {
project_id,
key,
env,
env_id,
org_id,
},
} => {
let r = client
.delete_env_var(
&project_id,
&key,
env.as_deref(),
env_id.as_deref(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
println!("deleted={} key={}", r.deleted, r.key);
})?;
}
Commands::Logs {
project,
environment,
log_type,
env,
tail,
level,
service,
grep,
org_id,
} => {
let list = client
.list_logs(
project.as_deref(),
environment.as_deref(),
Some(log_type.as_str()),
env.as_deref(),
Some(*tail),
level.as_deref(),
service.as_deref(),
grep.as_deref(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &list, || {
for line in &list.data {
println!(
"{}\t{}\t{}",
line.timestamp,
line.level.as_deref().unwrap_or("-"),
line.message
);
}
})?;
}
Commands::Resources {
command:
ResourceCommands::List {
kind,
project,
org_id,
},
} => {
let list = client
.list_resources(kind.as_deref(), project.as_deref(), None, org_id.as_deref())
.await?;
emit_json_or(&cli, &list, || {
for r in &list.data {
println!("{}\t{}\t{}\t{}", r.id, r.kind, r.name, r.status);
}
})?;
}
Commands::Resources {
command: ResourceCommands::Get { id, org_id },
} => {
let r = client.get_resource(&id, org_id.as_deref()).await?;
emit_json_or(&cli, &r, || {
println!("{}\t{}\t{}\t{}", r.id, r.kind, r.name, r.status);
})?;
}
Commands::Resources {
command:
ResourceCommands::Create {
kind,
name,
project,
tier,
storage,
org_id,
},
} => {
let r = client
.create_resource(
&kind,
&name,
project.as_deref(),
tier.as_deref(),
storage.as_ref().copied(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
if let Some(res) = r.resource.as_ref() {
println!("created id={} status={}", res.id, res.status);
}
})?;
}
Commands::Resources {
command: ResourceCommands::Delete { id, org_id },
} => {
let r = client.delete_resource(&id, org_id.as_deref()).await?;
emit_json_or(&cli, &r, || {
println!("deleted={} id={}", r.deleted, r.id);
})?;
}
Commands::Resources {
command:
ResourceCommands::Bind {
resource_id,
environment_id,
project,
org_id,
},
} => {
let r = client
.bind_resource(
&resource_id,
&environment_id,
project.as_deref(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
println!(
"bound={} resource={} env={}",
r.bound, r.resource_id, r.environment_id
);
})?;
}
Commands::Db {
command:
DbCommands::Create {
name,
project,
tier,
storage,
org_id,
},
} => {
let r = client
.create_resource(
"database",
&name,
project.as_deref(),
Some(&tier),
Some(*storage),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
if let Some(res) = r.resource.as_ref() {
println!("db id={} status={}", res.id, res.status);
}
})?;
}
Commands::Db {
command: DbCommands::List { project, org_id },
} => {
let list = client
.list_resources(
Some("database"),
project.as_deref(),
None,
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &list, || {
for r in &list.data {
println!("{}\t{}\t{}", r.id, r.name, r.status);
}
})?;
}
Commands::Db {
command: DbCommands::Get { id, org_id },
} => {
let r = client.get_resource(&id, org_id.as_deref()).await?;
emit_json_or(&cli, &r, || {
println!("{}\t{}\t{}", r.id, r.name, r.status);
})?;
}
Commands::Db {
command: DbCommands::Delete { id, org_id },
} => {
let r = client.delete_resource(&id, org_id.as_deref()).await?;
emit_json_or(&cli, &r, || {
println!("deleted={} id={}", r.deleted, r.id);
})?;
}
Commands::Db {
command:
DbCommands::Branch {
resource_id,
name,
env,
org_id,
},
} => {
let r = client
.branch_resource(&resource_id, &name, env.as_deref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &r, || {
if let Some(res) = r.resource.as_ref() {
println!("branch id={} status={}", res.id, res.status);
}
})?;
}
Commands::Storage {
command:
StorageCommands::Create {
project_id,
name,
env,
org_id,
},
} => {
let r = client
.create_project_storage(&project_id, &name, env.as_deref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &r, || {
if let Some(s) = r.storage.as_ref() {
println!(
"storage id={} url={}",
s.id,
s.public_url.as_deref().unwrap_or("-")
);
}
})?;
}
Commands::Storage {
command: StorageCommands::List { project_id, org_id },
} => {
let list = client
.list_project_storage(&project_id, org_id.as_deref())
.await?;
emit_json_or(&cli, &list, || {
for s in &list.data {
println!(
"{}\t{}\t{}",
s.id,
s.name,
s.public_url.as_deref().unwrap_or("-")
);
}
})?;
}
Commands::Tasks {
command: TaskCommands::List { project, org_id },
} => {
let list = client
.list_tasks(project.as_deref(), None, org_id.as_deref())
.await?;
emit_json_or(&cli, &list, || {
for t in &list.data {
println!("{}\t{}\t{}", t.id, t.name, t.status);
}
})?;
}
Commands::Tasks {
command:
TaskCommands::Create {
name,
project,
mode,
handler,
cron,
callback_url,
org_id,
},
} => {
let r = client
.create_task(
&name,
project.as_deref(),
mode.as_deref(),
handler.as_deref(),
cron.as_deref(),
callback_url.as_deref(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
if let Some(t) = r.task.as_ref() {
println!("task id={} status={}", t.id, t.status);
}
})?;
}
Commands::Tasks {
command: TaskCommands::Get { id, org_id },
} => {
let t = client.get_task(&id, org_id.as_deref()).await?;
emit_json_or(&cli, &t, || {
println!("{}\t{}\t{}", t.id, t.name, t.status);
})?;
}
Commands::Backup {
command: BackupCommands::List { resource, org_id },
} => {
let list = client
.list_backups(resource.as_deref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &list, || {
for b in &list.data {
println!(
"{}\t{}\t{}",
b.id,
b.status,
b.tag.as_deref().unwrap_or("-")
);
}
})?;
}
Commands::Backup {
command:
BackupCommands::Create {
resource,
tag,
org_id,
},
} => {
let r = client
.create_backup(resource.as_deref(), tag.as_deref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &r, || {
if let Some(b) = r.backup.as_ref() {
println!("backup id={} status={}", b.id, b.status);
}
})?;
}
Commands::Backup {
command:
BackupCommands::Restore {
backup_id,
resource,
org_id,
},
} => {
let r = client
.restore_backup(&backup_id, resource.as_deref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &r, || {
println!("restore id={} status={}", r.id, r.status);
})?;
}
Commands::Flags {
command: FlagCommands::List { project, org_id },
} => {
let list = client
.list_flags(project.as_deref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &list, || {
for f in &list.data {
println!("{}\tenabled={}", f.key, f.enabled);
}
})?;
}
Commands::Flags {
command:
FlagCommands::Create {
key,
description,
project,
enabled,
org_id,
},
} => {
let r = client
.create_flag(
&key,
description.as_deref(),
project.as_deref(),
enabled.as_ref().copied(),
org_id.as_deref(),
)
.await?;
emit_json_or(&cli, &r, || {
if let Some(f) = r.flag.as_ref() {
println!("flag key={} enabled={}", f.key, f.enabled);
}
})?;
}
Commands::Flags {
command:
FlagCommands::Toggle {
key,
enabled,
project,
org_id,
},
} => {
let r = client
.toggle_flag(&key, enabled.as_ref().copied(), project.as_deref(), org_id.as_deref())
.await?;
emit_json_or(&cli, &r, || {
if let Some(f) = r.flag.as_ref() {
println!("flag key={} enabled={}", f.key, f.enabled);
}
})?;
}
Commands::Previews {
command:
PreviewCommands::Cost {
project_id,
pr_number,
org_id,
},
} => {
let r = client
.get_preview_cost(&project_id, &pr_number, org_id.as_deref())
.await?;
emit_json_or(&cli, &r, || {
println!(
"pr={} total={} {}",
r.pr_number, r.total, r.currency
);
})?;
}
Commands::Api {
method,
path,
body,
input,
org_id,
} => {
let payload = load_body(body.as_deref(), input.as_ref())?;
let (status, value) = client
.api(
method.clone().to_reqwest(),
path,
payload.as_ref(),
org_id.as_deref(),
)
.await?;
if cli.json {
let envelope = json!({"status": status, "body": value});
println!("{}", serde_json::to_string_pretty(&envelope)?);
} else if !value.is_null() {
println!("{}", serde_json::to_string_pretty(&value)?);
}
}
Commands::VersionInfo => unreachable!(),
}
Ok(())
}
fn emit_json_or<T: serde::Serialize>(
cli: &Cli,
value: &T,
human: impl FnOnce(),
) -> anyhow::Result<()> {
if cli.json {
println!("{}", serde_json::to_string_pretty(value)?);
} else {
human();
}
Ok(())
}
fn load_body(
inline: Option<&str>,
input: Option<&PathBuf>,
) -> anyhow::Result<Option<Value>> {
match (inline, input) {
(Some(_), Some(_)) => Err(anyhow::anyhow!(
"use only one of --body or --input for request payload"
)),
(Some(s), None) => Ok(Some(serde_json::from_str(s)?)),
(None, Some(path)) => {
let text = if path.as_os_str() == "-" {
use std::io::Read;
let mut buf = String::new();
std::io::stdin().read_to_string(&mut buf)?;
buf
} else {
std::fs::read_to_string(path)?
};
if text.trim().is_empty() {
Ok(None)
} else {
Ok(Some(serde_json::from_str(&text)?))
}
}
(None, None) => Ok(None),
}
}
fn print_out<T: serde::Serialize>(cli: &Cli, value: &T, human: impl FnOnce()) {
if cli.json {
println!(
"{}",
serde_json::to_string_pretty(value).expect("serialize json")
);
} else {
human();
}
}