virtuoso_cli/commands/
session.rs1use crate::config::Config;
2use crate::error::{Result, VirtuosoError};
3use crate::models::SessionInfo;
4use crate::output::OutputFormat;
5use crate::transport::tunnel::SSHClient;
6use serde_json::{json, Value};
7
8pub fn list(format: OutputFormat) -> Result<Value> {
9 if let Ok(cfg) = Config::from_env() {
12 if cfg.is_remote() {
13 if let Ok(client) = SSHClient::from_env(cfg.keep_remote_files) {
14 let _ = SessionInfo::sync_from_remote(&client.runner);
15 }
16 }
17 }
18
19 let mut sessions = SessionInfo::list()
20 .map_err(|e| VirtuosoError::Execution(format!("failed to read sessions: {e}")))?;
21
22 let sessions_dir = SessionInfo::sessions_dir();
23 sessions.retain(|s| {
24 if s.is_alive() {
25 true
26 } else {
27 let _ = std::fs::remove_file(sessions_dir.join(format!("{}.json", s.id)));
28 false
29 }
30 });
31
32 if format == OutputFormat::Json {
33 return Ok(json!({
34 "status": "success",
35 "count": sessions.len(),
36 "sessions": sessions.iter().map(|s| json!({
37 "id": s.id,
38 "port": s.port,
39 "pid": s.pid,
40 "host": s.host,
41 "user": s.user,
42 "created": s.created,
43 })).collect::<Vec<_>>(),
44 }));
45 }
46
47 if sessions.is_empty() {
48 println!("No active Virtuoso sessions found.");
49 println!("Start Virtuoso and run RBStart() in CIW to register a session.");
50 return Ok(json!({"status": "success", "count": 0}));
51 }
52
53 println!(
54 "{:<20} {:>6} {:>7} {:<12} CREATED",
55 "SESSION ID", "PORT", "PID", "HOST"
56 );
57 println!("{}", "-".repeat(72));
58 for s in &sessions {
59 println!(
60 "{:<20} {:>6} {:>7} {:<12} {}",
61 s.id, s.port, s.pid, s.host, s.created
62 );
63 }
64
65 Ok(json!({"status": "success", "count": sessions.len()}))
66}
67
68pub fn current() -> Result<Value> {
69 let live = SessionInfo::list_alive();
70 match live.len() {
71 0 => Ok(json!({"status": "success", "session": null, "note": "no live sessions; VB_PORT will be used"})),
72 1 => Ok(json!({
73 "status": "success",
74 "session": live[0].id,
75 "port": live[0].port,
76 "auto_selected": true,
77 })),
78 _ => {
79 let ids: Vec<&str> = live.iter().map(|s| s.id.as_str()).collect();
80 Ok(json!({
81 "status": "ambiguous",
82 "sessions": ids,
83 "note": "use --session <id> to select one",
84 }))
85 }
86 }
87}
88
89pub fn cleanup() -> Result<Value> {
90 let all = SessionInfo::list().unwrap_or_default();
91 let dir = SessionInfo::sessions_dir();
92 let mut removed = Vec::new();
93 for s in &all {
94 if !s.is_alive() {
95 let path = dir.join(format!("{}.json", s.id));
96 if std::fs::remove_file(&path).is_ok() {
97 removed.push(s.id.clone());
98 }
99 }
100 }
101 Ok(json!({
102 "status": "success",
103 "removed": removed.len(),
104 "sessions": removed,
105 }))
106}
107
108pub fn history(id: &str, only_skill: bool, only_cmd: bool, limit: usize) -> Result<Value> {
109 let show_skill = !only_cmd;
110 let show_cmd = !only_skill;
111
112 let skill_entries: Vec<Value> = if show_skill {
113 crate::history::load_skill(id, limit)
114 .into_iter()
115 .map(|e| serde_json::json!({"type":"skill","ts":e.ts,"ok":e.ok,"skill":e.skill,"output":e.output}))
116 .collect()
117 } else {
118 vec![]
119 };
120
121 let cmd_entries: Vec<Value> = if show_cmd {
122 crate::history::load_cmd(Some(id), limit)
123 .into_iter()
124 .map(|e| serde_json::json!({"type":"cmd","ts":e.ts,"cmd":e.cmd,"exit_code":e.exit_code}))
125 .collect()
126 } else {
127 vec![]
128 };
129
130 Ok(json!({
131 "status": "success",
132 "session": id,
133 "skill_count": skill_entries.len(),
134 "cmd_count": cmd_entries.len(),
135 "skill": skill_entries,
136 "cmd": cmd_entries,
137 }))
138}
139
140pub fn show(id: &str, _format: OutputFormat) -> Result<Value> {
141 let s = SessionInfo::load(id)
142 .map_err(|e| VirtuosoError::NotFound(format!("session '{id}' not found: {e}")))?;
143
144 Ok(json!({
145 "status": "success",
146 "session": {
147 "id": s.id,
148 "port": s.port,
149 "pid": s.pid,
150 "host": s.host,
151 "user": s.user,
152 "created": s.created,
153 "alive": s.is_alive(),
154 }
155 }))
156}