computer_use/
shortcuts.rs1use std::time::Duration;
10
11use serde::Serialize;
12
13use crate::backend::command::stderr_snippet;
14use crate::error::ComputerError;
15use crate::protocol::APPLE_EVENT_TIMEOUT_SECS;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
19pub struct ShortcutsReport {
20 pub exit_status: i32,
22 pub stderr: String,
24}
25
26pub fn run(name: &str) -> Result<ShortcutsReport, ComputerError> {
30 let trimmed = name.trim();
31 if trimmed.is_empty() {
32 return Err(ComputerError::InvalidArguments(
33 "shortcuts run requires a workflow name".into(),
34 ));
35 }
36 let timeout = Duration::from_secs(APPLE_EVENT_TIMEOUT_SECS);
37 let output = crate::backend::command::run_timeout("shortcuts", &["run", trimmed], timeout)
38 .map_err(ComputerError::ExecutionFailed)?;
39 let exit_status = output.status.code().unwrap_or(-1);
40 let stderr = stderr_snippet(&output.stderr);
41 if !output.status.success() {
42 return Err(ComputerError::ExecutionFailed(format!(
43 "shortcuts run {trimmed} failed (exit {exit_status}): {stderr}"
44 )));
45 }
46 Ok(ShortcutsReport {
47 exit_status,
48 stderr,
49 })
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn empty_name_is_an_argument_error() {
58 let error = run("").unwrap_err();
59 assert!(matches!(error, ComputerError::InvalidArguments(_)));
60 }
61
62 #[test]
63 fn whitespace_name_is_an_argument_error() {
64 let error = run(" ").unwrap_err();
65 assert!(matches!(error, ComputerError::InvalidArguments(_)));
66 }
67}