| 500 | } |
| 501 | |
| 502 | fn parse_args(named_args: &[String], raw_args: &[String]) -> Result<serde_json::Value> { |
| 503 | let mut map = serde_json::Map::new(); |
| 504 | |
| 505 | // Parse named_args (from -a/--arg) first |
| 506 | for arg in named_args { |
| 507 | let (k, v) = arg |
| 508 | .split_once('=') |
| 509 | .ok_or_else(|| anyhow::anyhow!("Invalid argument '{arg}': expected key=value"))?; |
| 510 | let (k, val) = parse_kv(k, v, arg)?; |
| 511 | map.insert(k, val); |
| 512 | } |
| 513 | |
| 514 | // Parse raw_args (trailing positional arguments) |
| 515 | let mut positional_count = 0; |
| 516 | for arg in raw_args { |
| 517 | // Only treat `=` as a key/value separator when the part before it looks |
| 518 | // like an intentional key. Otherwise a positional value containing a |
| 519 | // literal `=` (e.g. a URL query string) would be misparsed as key=value. |
| 520 | let kv = arg.split_once('=').filter(|(k, _)| looks_like_arg_key(k.trim())); |
| 521 | if let Some((k, v)) = kv { |
| 522 | let (k, val) = parse_kv(k, v, arg)?; |
| 523 | map.insert(k, val); |
| 524 | } else { |
| 525 | // Positional argument without a recognized `key=` |
| 526 | let val = parse_json_value(arg.trim()); |
| 527 | |
| 528 | // Populated as `_0`, `_1`, etc. |
| 529 | map.insert(format!("_{}", positional_count), val.clone()); |
| 530 | |
| 531 | // If it's the first positional argument, also map it to `"query"` |
| 532 | if positional_count == 0 { |
| 533 | map.insert("query".to_string(), val); |
| 534 | } |
| 535 | positional_count += 1; |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | Ok(serde_json::Value::Object(map)) |
| 540 | } |
| 541 | |
| 542 | fn build_request(cli: &Cli) -> Result<DaemonRequest> { |
| 543 | // Resolve relative file paths to absolute so the daemon (which retains its |