robin/scripts/
script_runner.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::process::Command;
use std::path::PathBuf;
use anyhow::{Result, Context, anyhow};
use colored::*;
use inquire::Select;
use serde_json;

use crate::config::RobinConfig;
use crate::utils::send_notification;

pub fn run_script(script: &serde_json::Value, notify: bool) -> Result<()> {
    let start_time = std::time::Instant::now();
    
    match script {
        serde_json::Value::String(cmd) => {
            let status = if cfg!(target_os = "windows") {
                Command::new("cmd")
                    .args(["/C", cmd])
                    .status()
            } else {
                Command::new("sh")
                    .arg("-c")
                    .arg(cmd)
                    .status()
            }.with_context(|| format!("Failed to execute script: {}", cmd))?;

            if notify {
                let duration = start_time.elapsed();
                let success = status.success();
                let message = if success {
                    format!("Completed in {:.1}s", duration.as_secs_f32())
                } else {
                    "Failed".to_string()
                };
                
                send_notification(
                    "Robin",
                    &format!("Command '{}' {}", cmd.split_whitespace().next().unwrap_or(cmd), message),
                    success,
                )?;
            }

            if !status.success() {
                println!("{}", "Script failed!".red());
                return Err(anyhow!("Script failed: {}", cmd));
            }
            Ok(())
        },
        serde_json::Value::Array(commands) => {
            for cmd in commands {
                if let Some(cmd_str) = cmd.as_str() {
                    let status = if cfg!(target_os = "windows") {
                        Command::new("cmd")
                            .args(["/C", cmd_str])
                            .status()
                    } else {
                        Command::new("sh")
                            .arg("-c")
                            .arg(cmd_str)
                            .status()
                    }.with_context(|| format!("Failed to execute script: {}", cmd_str))?;

                    if !status.success() {
                        println!("{}", format!("Script failed: {}", cmd_str).red());
                        return Err(anyhow!("Script failed: {}", cmd_str));
                    }
                }
            }

            if notify {
                let duration = start_time.elapsed();
                send_notification(
                    "Robin",
                    &format!("Command sequence completed in {:.1}s", duration.as_secs_f32()),
                    true,
                )?;
            }
            Ok(())
        },
        _ => Err(anyhow!("Invalid script type: must be string or array of strings")),
    }
}

pub fn list_commands(config_path: &PathBuf) -> Result<()> {
    let config = RobinConfig::load(config_path)
        .with_context(|| "No .robin.json found. Run 'robin init' first")?;

    for (name, script) in &config.scripts {
        match script {
            serde_json::Value::String(cmd) => {
                println!("==> {} # {}", name.blue(), cmd);
            },
            serde_json::Value::Array(commands) => {
                println!("==> {} # [", name.blue());
                for cmd in commands {
                    if let Some(cmd_str) = cmd.as_str() {
                        println!("       {}", cmd_str);
                    }
                }
                println!("     ]");
            },
            _ => println!("==> {} # <invalid script type>", name.blue()),
        }
    }

    Ok(())
}

pub fn interactive_mode(config_path: &PathBuf) -> Result<()> {
    let config = RobinConfig::load(config_path)
        .with_context(|| "No .robin.json found. Run 'robin init' first")?;

    let commands: Vec<String> = config.scripts.keys().cloned().collect();
    if commands.is_empty() {
        println!("{}", "No commands available".red());
        return Ok(());
    }

    let selection = Select::new("Select a command to run:", commands).prompt()?;
    if let Some(script) = config.scripts.get(&selection) {
        run_script(script, false)?;
    }

    Ok(())
}