use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, Subcommand};
use auths_sdk::storage_layout::layout;
use crate::commands::executable::ExecutableCommand;
use crate::config::CliConfig;
use crate::ux::format::{JsonResponse, is_json_mode};
#[derive(Parser, Debug, Clone)]
#[command(
about = "Aggregate treasury cap: allot bounded slices across sub-agents, reallocatable, with Σ ≤ cap.",
after_help = "Examples:
auths treasury open --manager my-key --cap calls:10
auths treasury allot --manager my-key --to did:keri:E… --amount 4
auths treasury reallocate --manager my-key --from did:keri:E… --to did:keri:E… --amount 2
auths treasury status --manager my-key"
)]
pub struct TreasuryCommand {
#[clap(subcommand)]
pub subcommand: TreasurySubcommand,
}
#[derive(Subcommand, Debug, Clone)]
pub enum TreasurySubcommand {
Open {
#[arg(long, help = "Manager keychain alias (the ledger key).")]
manager: String,
#[arg(long, help = "Aggregate cap (calls:<N> or a plain integer).")]
cap: String,
},
Allot {
#[arg(long)]
manager: String,
#[arg(long = "to", help = "Sub-agent did:keri: to allot to.")]
to: String,
#[arg(long)]
amount: u64,
},
Reallocate {
#[arg(long)]
manager: String,
#[arg(long, help = "Source sub-agent did:keri:.")]
from: String,
#[arg(long = "to", help = "Target sub-agent did:keri:.")]
to: String,
#[arg(long)]
amount: u64,
},
Status {
#[arg(long)]
manager: String,
},
Credit {
#[arg(long = "to", help = "Sub-agent did:keri: to credit.")]
to: String,
#[arg(long, help = "Path to a recorded x402 SettlementResponse JSON.")]
settlement: PathBuf,
#[arg(
long = "claim-cents",
help = "Assert the credited amount (rejected if ≠ the recorded settlement)."
)]
claim_cents: Option<u64>,
},
Subdelegate {
#[arg(long)]
manager: String,
#[arg(
long,
help = "The sub-agent (parent) did:keri: doing the sub-delegation."
)]
parent: String,
#[arg(long, help = "The child worker did:keri:.")]
child: String,
#[arg(long)]
amount: u64,
},
Reclaim {
#[arg(long)]
manager: String,
#[arg(long = "agent", help = "The revoked sub-agent did:keri: to reclaim.")]
agent: String,
},
}
impl ExecutableCommand for TreasuryCommand {
fn execute(&self, ctx: &CliConfig) -> Result<()> {
let repo_path = layout::resolve_repo_path(ctx.repo_path.clone())?;
handle_treasury(self.clone(), repo_path)
}
}
fn parse_cap(s: &str) -> Result<u64> {
let raw = s.strip_prefix("calls:").unwrap_or(s).trim();
raw.parse::<u64>().map_err(|_| {
anyhow::anyhow!("invalid cap {s:?}: expected calls:<N> or a non-negative integer")
})
}
pub fn handle_treasury(cmd: TreasuryCommand, repo_path: PathBuf) -> Result<()> {
use auths_sdk::domains::treasury;
match cmd.subcommand {
TreasurySubcommand::Open { manager, cap } => {
let parent_cap = parse_cap(&cap)?;
let v = treasury::open(&repo_path, &manager, parent_cap).map_err(anyhow::Error::new)?;
emit(
"treasury open",
serde_json::json!({ "status": v.status(), "manager": manager, "parent_cap": parent_cap }),
&format!("treasury cap established for {manager}: {parent_cap}"),
)
}
TreasurySubcommand::Allot {
manager,
to,
amount,
} => {
let v =
treasury::allot(&repo_path, &manager, &to, amount).map_err(anyhow::Error::new)?;
emit(
"treasury allot",
serde_json::json!({ "status": v.status(), "manager": manager, "to": to, "amount": amount }),
&format!("allot {amount} → {to}: {}", v.status()),
)
}
TreasurySubcommand::Reallocate {
manager,
from,
to,
amount,
} => {
let v = treasury::reallocate(&repo_path, &manager, &from, &to, amount)
.map_err(anyhow::Error::new)?;
emit(
"treasury reallocate",
serde_json::json!({ "status": v.status(), "manager": manager, "from": from, "to": to, "amount": amount }),
&format!("reallocate {amount} {from}→{to}: {}", v.status()),
)
}
TreasurySubcommand::Status { manager } => {
let st = treasury::status(&repo_path, &manager).map_err(anyhow::Error::new)?;
if is_json_mode() {
JsonResponse::success("treasury status", &st).print()?;
} else {
println!(
"treasury {manager}: cap {} · committed {} · free {} · {}",
st.parent_cap, st.committed, st.free_pool, st.status
);
for s in &st.slices {
println!(" slice {} = {}", s.agent_did, s.amount);
}
}
Ok(())
}
TreasurySubcommand::Credit {
to,
settlement,
claim_cents,
} => {
let v = treasury::credit(&repo_path, &to, &settlement, claim_cents)
.map_err(anyhow::Error::new)?;
emit(
"treasury credit",
serde_json::json!({ "status": v.status(), "to": to, "credited_cents": v.credited_cents(), "rail": "x402", "direction": "inbound" }),
&format!(
"credit {to}: {} ({}c inbound)",
v.status(),
v.credited_cents()
),
)
}
TreasurySubcommand::Subdelegate {
manager,
parent,
child,
amount,
} => {
let v = treasury::subdelegate(&repo_path, &manager, &parent, &child, amount)
.map_err(anyhow::Error::new)?;
emit(
"treasury subdelegate",
serde_json::json!({ "status": v.status(), "manager": manager, "parent": parent, "child": child, "amount": amount }),
&format!("subdelegate {amount} {parent}→{child}: {}", v.status()),
)
}
TreasurySubcommand::Reclaim { manager, agent } => {
let v = treasury::reclaim(&repo_path, &manager, &agent).map_err(anyhow::Error::new)?;
emit(
"treasury reclaim",
serde_json::json!({ "status": v.status(), "manager": manager, "agent": agent }),
&format!("reclaim {agent}: {}", v.status()),
)
}
}
}
fn emit(command: &str, data: serde_json::Value, human: &str) -> Result<()> {
if is_json_mode() {
JsonResponse::success(command, data).print()?;
} else {
println!("✓ {human}");
}
Ok(())
}