claude-print/src/cli.rs
jedarden 6f67cd5b00 Phase 1: crate scaffold with CLI, config, and error types
Adds Cargo.toml with pinned deps (clap, anyhow, serde, thiserror,
toml), src/{main,lib,cli,config,error}.rs. --version prints
"claude-print <ver> (wrapping claude <ver>)". All lib tests pass.
Builds clean for x86_64-unknown-linux-musl.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 16:00:19 -04:00

121 lines
3.2 KiB
Rust

use clap::{Parser, ValueEnum};
const VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Clone, ValueEnum)]
pub enum OutputFormat {
Text,
Json,
#[value(name = "stream-json")]
StreamJson,
}
impl std::fmt::Display for OutputFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutputFormat::Text => write!(f, "text"),
OutputFormat::Json => write!(f, "json"),
OutputFormat::StreamJson => write!(f, "stream-json"),
}
}
}
#[derive(Debug, Parser)]
#[command(
name = "claude-print",
about = "Drop-in replacement for `claude -p` billing against the subscription pool",
version = VERSION,
long_version = VERSION,
disable_version_flag = true,
)]
pub struct Cli {
/// Prompt string (mutually exclusive with --input-file and stdin)
pub prompt: Option<String>,
/// Read prompt from file
#[arg(long = "input-file", short = 'f')]
pub input_file: Option<std::path::PathBuf>,
/// Model to use (default: claude-sonnet-4-6)
#[arg(long, short = 'm')]
pub model: Option<String>,
/// Maximum number of turns (default: 30)
#[arg(long, default_value = "30")]
pub max_turns: u32,
/// Output format
#[arg(long = "output-format", short = 'o', default_value = "text")]
pub output_format: OutputFormat,
/// Comma-separated list of allowed tools
#[arg(long = "allowedTools")]
pub allowed_tools: Option<String>,
/// Comma-separated list of disallowed tools
#[arg(long = "disallowedTools")]
pub disallowed_tools: Option<String>,
/// Skip permission prompts (dangerous)
#[arg(long = "dangerously-skip-permissions")]
pub dangerously_skip_permissions: bool,
/// Wall-clock timeout in seconds (default: 3600)
#[arg(long, default_value = "3600")]
pub timeout: u64,
/// Path to claude binary (default: resolved from PATH)
#[arg(long = "claude-binary")]
pub claude_binary: Option<std::path::PathBuf>,
/// Disable user hook inheritance
#[arg(long = "no-inherit-hooks")]
pub no_inherit_hooks: bool,
/// Write timing traces to stderr
#[arg(long)]
pub verbose: bool,
/// Run installation self-test and exit
#[arg(long)]
pub check: bool,
/// Print version and exit
#[arg(long = "version", short = 'V')]
pub version: bool,
}
pub fn version_string(claude_version: Option<&str>) -> String {
let claude_part = match claude_version {
Some(v) => v.to_string(),
None => "not found".to_string(),
};
format!("claude-print {} (wrapping claude {})", VERSION, claude_part)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_string_with_claude_version() {
let s = version_string(Some("2.1.3"));
assert!(s.starts_with("claude-print "));
assert!(s.contains("wrapping claude 2.1.3"));
}
#[test]
fn version_string_without_claude() {
let s = version_string(None);
assert!(s.contains("not found"));
}
#[test]
fn version_format_matches_expected_pattern() {
let s = version_string(Some("2.0.0"));
assert_eq!(
s,
format!("claude-print {} (wrapping claude 2.0.0)", VERSION)
);
}
}