synaps_cli/tools/shell/
start.rs1use serde_json::{json, Value};
4use crate::{Result, RuntimeError};
5use crate::tools::{Tool, ToolContext};
6
7pub struct ShellStartTool;
8
9#[async_trait::async_trait]
10impl Tool for ShellStartTool {
11 fn name(&self) -> &str { "shell_start" }
12
13 fn description(&self) -> &str {
14 "Start a new interactive shell session with a PTY. Returns a session ID and the initial output. Use shell_send to interact and shell_end to close."
15 }
16
17 fn parameters(&self) -> Value {
18 json!({
19 "type": "object",
20 "properties": {
21 "command": {
22 "type": "string",
23 "description": "Command to run (default: user's default shell). Examples: 'bash', 'python3', 'ssh user@host'"
24 },
25 "working_directory": {
26 "type": "string",
27 "description": "Working directory for the session (default: current directory)"
28 },
29 "env": {
30 "type": "object",
31 "description": "Additional environment variables as key-value pairs",
32 "additionalProperties": { "type": "string" }
33 },
34 "rows": {
35 "type": "integer",
36 "description": "Terminal rows (default: from config, fallback 24)"
37 },
38 "cols": {
39 "type": "integer",
40 "description": "Terminal columns (default: from config, fallback 80)"
41 },
42 "readiness_timeout_ms": {
43 "type": "integer",
44 "description": "Override output readiness timeout for this session (ms)"
45 },
46 "idle_timeout": {
47 "type": "integer",
48 "description": "Override idle timeout for this session (seconds)"
49 }
50 },
51 "required": []
52 })
53 }
54
55 async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String> {
56 let mgr = ctx.capabilities.session_manager.as_ref()
57 .ok_or_else(|| RuntimeError::Tool("Shell sessions not available".into()))?;
58
59 let command = params["command"].as_str().map(|s| s.to_string());
60 let working_directory = params["working_directory"].as_str().map(|s| s.to_string());
61 let rows = params["rows"].as_u64().map(|r| r as u16);
62 let cols = params["cols"].as_u64().map(|c| c as u16);
63 let readiness_timeout_ms = params["readiness_timeout_ms"].as_u64();
64 let idle_timeout = params["idle_timeout"].as_u64();
65
66 let env = params["env"].as_object()
67 .map(|obj| obj.iter()
68 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
69 .collect())
70 .unwrap_or_default();
71
72 let opts = super::SessionOpts {
73 command, working_directory, env, rows, cols,
74 readiness_timeout_ms, idle_timeout,
75 };
76
77 let (session_id, output, status) = mgr.create_session(opts, ctx.channels.tx_delta.as_ref()).await?;
78
79 let mut result = format!("[Session {} | {}]\n", session_id, status);
80 if !output.is_empty() {
81 result.push_str(&output);
82 }
83 Ok(result)
84 }
85}