From 2950b681afe0312b4e6cfa1e0fe1496a4612b247 Mon Sep 17 00:00:00 2001 From: Vitor Py Date: Sat, 18 Oct 2025 16:45:52 +0200 Subject: [PATCH] Add support for multiline secrets via stdin and file input Support reading secret values from stdin or files to handle multiline content like SSH keys, certificates, and config files. New value patterns: - `--value -` reads from stdin - `--value @` reads from file - `--value ` uses literal value (backward compatible) File path handling: - Supports tilde expansion for home directory (~/) - Provides clear error messages if file cannot be read Examples: # From stdin cat ~/.ssh/id_ed25519 | tangled spindle secret add \ --repo myrepo --key SSH_KEY --value - # From file tangled spindle secret add --repo myrepo \ --key SSH_KEY --value @~/.ssh/id_ed25519 # Literal value (existing behavior) tangled spindle secret add --repo myrepo \ --key API_KEY --value "my-secret-key" Fixes issue where multiline values were split into multiple arguments by the shell, causing clap parsing errors. --- crates/tangled-cli/src/cli.rs | 2 +- crates/tangled-cli/src/commands/spindle.rs | 27 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/crates/tangled-cli/src/cli.rs b/crates/tangled-cli/src/cli.rs index 2c71f9b..efe9fd1 100644 --- a/crates/tangled-cli/src/cli.rs +++ b/crates/tangled-cli/src/cli.rs @@ -381,7 +381,7 @@ pub struct SpindleSecretAddArgs { /// Secret key #[arg(long)] pub key: String, - /// Secret value + /// Secret value (use '@filename' to read from file, '-' to read from stdin) #[arg(long)] pub value: String, } diff --git a/crates/tangled-cli/src/commands/spindle.rs b/crates/tangled-cli/src/commands/spindle.rs index 37e5cc2..e03f48c 100644 --- a/crates/tangled-cli/src/commands/spindle.rs +++ b/crates/tangled-cli/src/commands/spindle.rs @@ -250,7 +250,32 @@ async fn secret_add(args: SpindleSecretAddArgs) -> Result<()> { .unwrap_or_else(|| "https://spindle.tangled.sh".to_string()); let api = tangled_api::TangledClient::new(&spindle_base); - api.add_repo_secret(&pds, &session.access_jwt, &repo_at, &args.key, &args.value) + // Handle special value patterns: @file or - (stdin) + let value = if args.value == "-" { + // Read from stdin + use std::io::Read; + let mut buffer = String::new(); + std::io::stdin().read_to_string(&mut buffer)?; + buffer + } else if let Some(path) = args.value.strip_prefix('@') { + // Read from file, expand ~ if needed + let expanded_path = if path.starts_with("~/") { + if let Some(home) = std::env::var("HOME").ok() { + path.replacen("~/", &format!("{}/", home), 1) + } else { + path.to_string() + } + } else { + path.to_string() + }; + std::fs::read_to_string(&expanded_path) + .map_err(|e| anyhow!("Failed to read file '{}': {}", expanded_path, e))? + } else { + // Use value as-is + args.value + }; + + api.add_repo_secret(&pds, &session.access_jwt, &repo_at, &args.key, &value) .await?; println!("Added secret '{}' to {}", args.key, args.repo); Ok(()) -- 2.51.2