//! `pic secrets` subcommands: `ls`, `set`, `rm`. //! //! Set reads the secret value from stdin (the only safe channel — //! inline values would leak into shell history). The value is sent //! as a JSON string; pass `--json` to interpret stdin as raw JSON //! (numbers, maps, …) instead. use std::io::Read; use anyhow::{anyhow, Context, Result}; use crate::client::Client; use crate::config; use crate::output::{OutputMode, Table}; pub async fn ls(app: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let resp = client.secrets_list(app).await?; let mut table = Table::new(["name", "updated_at"]); for s in resp.secrets { table.row([s.name, s.updated_at.to_rfc3339()]); } table.print(mode); Ok(()) } pub async fn set(app: &str, name: &str, as_json: bool) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let mut buf = String::new(); std::io::stdin() .read_to_string(&mut buf) .context("read secret value from stdin")?; let trimmed = buf.trim_end_matches(['\n', '\r']); if trimmed.is_empty() { return Err(anyhow!("empty stdin — secret value must not be empty")); } let value = if as_json { serde_json::from_str(trimmed).map_err(|e| anyhow!("parse stdin as JSON: {e}"))? } else { serde_json::Value::String(trimmed.to_string()) }; client.secrets_set(app, name, value).await?; println!("Set secret {name}"); Ok(()) } pub async fn rm(app: &str, name: &str) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; client.secrets_delete(app, name).await?; println!("Deleted secret {name}"); Ok(()) }