Convert opencode transcripts to otel (or agent) traces
at main 182 lines 4.6 kB view raw
1use anyhow::Result; 2use clap::{Parser, Subcommand, ValueEnum}; 3use std::path::PathBuf; 4 5#[derive(Parser)] 6#[command( 7 name = "exp2span", 8 version = env!("CARGO_PKG_VERSION"), 9 author = "rektide de la faye", 10 about = "Convert opencode logs to OpenTelemetry traces", 11 long_about = "Parse opencode esport markdown logs and export them as OpenTelemetry spans using MCP semantic conventions." 12)] 13pub struct Cli { 14 /// Global configuration file 15 #[arg(short, long, global = true, env = "EXP2SPAN_CONFIG")] 16 pub config: Option<PathBuf>, 17 18 /// Output format 19 #[arg( 20 short, 21 long, 22 global = true, 23 value_enum, 24 default_value_t = OutputFormat::Otlp, 25 env = "EXP2SPAN_OUTPUT_FORMAT" 26 )] 27 pub output: OutputFormat, 28 29 /// Increase logging verbosity 30 #[arg(short, long, action = clap::ArgAction::Count, global = true)] 31 pub verbose: u8, 32 33 /// Suppress all output 34 #[arg(short, long, global = true, conflicts_with = "verbose")] 35 pub quiet: bool, 36 37 #[command(subcommand)] 38 pub command: Commands, 39} 40 41#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)] 42pub enum OutputFormat { 43 /// Export to OTLP (default behavior) 44 Otlp, 45 /// Pretty-printed JSON output 46 Json, 47 /// JSON Lines (NDJSON) for streaming 48 JsonLines, 49 /// YAML output 50 Yaml, 51 /// Human-readable table format 52 Table, 53} 54 55#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)] 56pub enum OtlpProtocol { 57 /// Use HTTP/JSON for OTLP export 58 Http, 59 /// Use gRPC for OTLP export 60 Grpc, 61} 62 63impl std::fmt::Display for OtlpProtocol { 64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 match self { 66 OtlpProtocol::Http => write!(f, "http"), 67 OtlpProtocol::Grpc => write!(f, "grpc"), 68 } 69 } 70} 71 72#[derive(Subcommand)] 73pub enum Commands { 74 /// Export log to OpenTelemetry spans 75 Export(ExportArgs), 76 77 /// Validate log file format 78 Validate(ValidateArgs), 79 80 /// Show log metadata and statistics 81 Info(InfoArgs), 82 83 /// Generate shell completions 84 Completion(CompletionArgs), 85} 86 87#[derive(Parser, Debug)] 88pub struct ExportArgs { 89 /// Path to the log file 90 #[arg(value_name = "FILE")] 91 pub file: PathBuf, 92 93 /// Dry run - don't export, just show what would be done 94 #[arg(long)] 95 pub dry_run: bool, 96 97 /// Filter by role (user, assistant) 98 #[arg(short, long)] 99 pub filter_role: Option<String>, 100 101 /// OTLP endpoint (e.g., http://localhost:4318 for HTTP, http://localhost:4317 for gRPC) 102 #[arg(long, default_value = "http://localhost:4318")] 103 pub otlp_endpoint: String, 104 105 /// OTLP protocol to use 106 #[arg(long, default_value_t = OtlpProtocol::Http)] 107 pub otlp_protocol: OtlpProtocol, 108 109 /// Custom headers for OTLP export (e.g., "Authorization=Bearer $TOKEN") 110 #[arg(long, value_parser = parse_key_value)] 111 pub otlp_header: Vec<(String, String)>, 112 113 /// Service name for the traces 114 #[arg(long, default_value = "exp2span")] 115 pub otlp_service_name: String, 116 117 /// Batch size for span export 118 #[arg(long, default_value_t = 100)] 119 pub batch_size: usize, 120 121 /// Timeout for OTLP export in seconds 122 #[arg(long, default_value_t = 30)] 123 pub otlp_timeout_secs: u64, 124 125 /// Maximum number of retry attempts for failed exports 126 #[arg(long, default_value_t = 3)] 127 pub otlp_max_retries: u32, 128 129 /// Initial delay between retries in milliseconds 130 #[arg(long, default_value_t = 100)] 131 pub otlp_retry_delay_ms: u64, 132} 133 134fn parse_key_value(s: &str) -> Result<(String, String)> { 135 let parts: Vec<&str> = s.splitn(2, '=').collect(); 136 if parts.len() != 2 { 137 return Err(anyhow::anyhow!( 138 "Invalid key-value pair: '{}'. Expected format: KEY=VALUE", 139 s 140 )); 141 } 142 Ok((parts[0].to_string(), parts[1].to_string())) 143} 144 145#[derive(Parser, Debug)] 146pub struct ValidateArgs { 147 /// Path to the log file 148 #[arg(value_name = "FILE")] 149 pub file: PathBuf, 150} 151 152#[derive(Parser, Debug)] 153pub struct InfoArgs { 154 /// Path to log file 155 #[arg(value_name = "FILE")] 156 pub file: PathBuf, 157} 158 159#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)] 160pub enum CompletionShell { 161 /// Bash shell 162 Bash, 163 /// Zsh shell 164 Zsh, 165 /// Fish shell 166 Fish, 167 /// PowerShell 168 PowerShell, 169 /// Elvish shell 170 Elvish, 171} 172 173#[derive(Parser, Debug)] 174pub struct CompletionArgs { 175 /// Shell to generate completions for 176 #[arg(value_enum)] 177 pub shell: CompletionShell, 178 179 /// Print completions to stdout instead of installing 180 #[arg(short, long)] 181 pub print: bool, 182}