Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions src/api/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,68 @@ pub struct BillingInfo {
pub monthly_usage: u64,
pub monthly_limit: u64,
pub is_active: bool,
pub plan: Plan,
#[serde(default)]
pub is_past_due: bool,
/// Suno no longer nests the active plan under a `plan` key. Instead
/// `subscription_type` is `false` on the free tier or the plan's
/// `plan_key` string (e.g. `"pro"`) when subscribed, and the full catalog
/// of plans (with pricing/features) is listed separately in `plans`.
#[serde(default)]
pub subscription_type: SubscriptionType,
pub models: Vec<Model>,
pub period: String,
pub renews_on: Option<String>,
#[serde(default)]
pub plans: Vec<PlanOption>,
#[serde(default)]
pub accessible_features: Vec<Feature>,
#[serde(default)]
pub remaster_model_types: Vec<RemasterModelInfo>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Plan {
pub name: String,
#[serde(untagged)]
pub enum SubscriptionType {
/// No active paid subscription (free tier).
None(bool),
/// Active plan, identified by its `plan_key` (e.g. "pro").
Plan(String),
}

impl Default for SubscriptionType {
fn default() -> Self {
SubscriptionType::None(false)
}
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PlanOption {
pub plan_key: String,
pub name: String,
#[serde(default)]
pub usage_plan_features: Vec<Feature>,
}

impl BillingInfo {
/// Human-readable name of the account's current plan, resolved from
/// `subscription_type` against the `plans` catalog (falls back to the
/// raw plan key, or "Free" when there's no active subscription).
pub fn plan_name(&self) -> String {
match &self.subscription_type {
SubscriptionType::Plan(key) => self
.plans
.iter()
.find(|p| &p.plan_key == key)
.map(|p| p.name.clone())
.unwrap_or_else(|| key.clone()),
SubscriptionType::None(_) => self
.plans
.iter()
.find(|p| p.plan_key == "free")
.map(|p| p.name.clone())
.unwrap_or_else(|| "Free".to_string()),
}
}
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Feature {
pub name: String,
Expand Down
8 changes: 8 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ pub struct DescribeArgs {
#[arg(short, long)]
pub prompt: String,

/// Song title
#[arg(short, long)]
pub title: Option<String>,

/// Style tags (optional, guides the generation)
#[arg(long)]
pub tags: Option<String>,
Expand Down Expand Up @@ -494,6 +498,8 @@ pub enum ModelVersion {
V45Plus,
#[value(name = "v4.5")]
V45,
#[value(name = "v4.5-all")]
V45All,
#[value(name = "v4")]
V4,
#[value(name = "v3.5")]
Expand All @@ -511,6 +517,7 @@ impl ModelVersion {
Self::V5 => "chirp-crow",
Self::V45Plus => "chirp-bluejay",
Self::V45 => "chirp-auk",
Self::V45All => "chirp-auk-turbo",
Self::V4 => "chirp-v4",
Self::V35 => "chirp-v3-5",
Self::V3 => "chirp-v3-0",
Expand All @@ -524,6 +531,7 @@ impl ModelVersion {
Self::V5 => "v5",
Self::V45Plus => "v4.5+",
Self::V45 => "v4.5",
Self::V45All => "v4.5-all",
Self::V4 => "v4",
Self::V35 => "v3.5",
Self::V3 => "v3",
Expand Down
7 changes: 5 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ async fn run() -> Result<(), CliError> {
}
eprintln!(
"Authenticated! Plan: {}, Credits: {}",
info.plan.name, info.total_credits_left
info.plan_name(),
info.total_credits_left
);
}

Expand Down Expand Up @@ -344,6 +345,7 @@ async fn run() -> Result<(), CliError> {
// text is sent in the same `prompt` field as custom mode.
let mut req = GenerateRequest::new(args.model.to_api_key(), "inspiration");
req.prompt = args.prompt;
req.title = args.title;
req.tags = tags;
req.make_instrumental = args.instrumental;
req.persona_id = args.persona.clone();
Expand Down Expand Up @@ -609,7 +611,8 @@ async fn run() -> Result<(), CliError> {
let info = client.billing_info().await?;
eprintln!(
"Auth: OK — {}, {} credits",
info.plan.name, info.total_credits_left
info.plan_name(),
info.total_credits_left
);
}
Err(CliError::AuthExpired) => {
Expand Down
6 changes: 1 addition & 5 deletions src/output/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,13 @@ pub fn billing(info: &BillingInfo) {
.apply_modifier(UTF8_ROUND_CORNERS)
.set_header(vec!["Field", "Value"]);

table.add_row(vec!["Plan", &info.plan.name]);
table.add_row(vec!["Plan", &info.plan_name()]);
table.add_row(vec!["Credits Left", &info.total_credits_left.to_string()]);
table.add_row(vec![
"Monthly Usage",
&format!("{} / {}", info.monthly_usage, info.monthly_limit),
]);
table.add_row(vec!["Active", &info.is_active.to_string()]);
table.add_row(vec!["Period", &info.period]);
if let Some(ref renew) = info.renews_on {
table.add_row(vec!["Renews On", renew]);
}
println!("{table}");
}

Expand Down