diff --git a/apps/soundcraft-cli/src/main.rs b/apps/soundcraft-cli/src/main.rs index 26d3cf6..5840b6d 100644 --- a/apps/soundcraft-cli/src/main.rs +++ b/apps/soundcraft-cli/src/main.rs @@ -295,6 +295,39 @@ fn describe(args: &[String]) -> ExitCode { } } +/// Control-channel methods. Every other dotted id is an engine command. +fn is_control_method(method: &str) -> bool { + matches!( + method, + "engine.execute" + | "command" + | "engine.commands" + | "engine.parity" + | "session.inspect" + | "document.inspect" + | "ui.inspect" + | "ui.menu.list" + | "ui.menu.invoke" + | "ui.set" + | "ui.move" + | "ui.click" + | "ui.drag" + | "ui.key" + | "ui.text" + | "ui.screenshot" + | "app.quit" + ) +} + +/// A command id is shorthand for `engine.execute`. Documented control methods stay methods. +fn app_request(method: &str, params: Value) -> (String, Value) { + if is_control_method(method) || !method.contains('.') { + (method.to_string(), params) + } else { + ("engine.execute".to_string(), json!({"command": method, "params": params})) + } +} + fn app(args: &[String]) -> ExitCode { let port = arg_value(args, "--port").or_else(|| std::env::var("SOUNDCRAFT_CONTROL_PORT").ok()).unwrap_or_else(|| "7979".into()); let positional: Vec<&String> = args @@ -312,17 +345,8 @@ fn app(args: &[String]) -> ExitCode { None => json!({}), }; let mut r = Remote::new(&port); - // A bare command id is shorthand for engine.execute. - let (m, p) = if method.contains('.') - && !method.starts_with("ui.") - && !method.starts_with("engine.") - && !method.starts_with("session.") - && !method.starts_with("app.") - { - ("engine.execute".to_string(), json!({"command": method, "params": params})) - } else { - (method.to_string(), params) - }; + // A bare command id is shorthand for engine.execute. Control-channel methods stay methods. + let (m, p) = app_request(method, params); match r.call(&m, p) { Ok(v) => { outln!("{}", serde_json::to_string_pretty(&v).unwrap_or_default()); @@ -381,6 +405,23 @@ fn parity(args: &[String]) -> ExitCode { mod tests { use super::*; + #[test] + fn command_ids_are_execute_shorthand_and_control_methods_stay_methods() { + let (m, p) = app_request("engine.history", json!({})); + assert_eq!(m, "engine.execute"); + assert_eq!(p["command"], "engine.history"); + let (m, p) = app_request("ui.theme", json!({"mode": "light"})); + assert_eq!(m, "engine.execute"); + assert_eq!(p["params"]["mode"], "light"); + let (m, _) = app_request("session.new", json!({"name": "Session A"})); + assert_eq!(m, "engine.execute"); + let (m, _) = app_request("track.new", json!({})); + assert_eq!(m, "engine.execute"); + for method in ["session.inspect", "engine.commands", "engine.parity", "ui.screenshot", "app.quit"] { + assert_eq!(app_request(method, json!({})).0, method); + } + } + #[test] fn missing_media_gets_one_warning_each() { assert!(missing_warnings(&[]).is_empty());